Skip to content

Card

The raised surface on the recessed page. Split it into parts with a header, body and footer, never by nesting cards.

LayoutServer-rendered

Examples

Header, body and footer

Header and footer are separated by hairlines. The footer holds a hint on the left and buttons on the right.

Connection

How agents reach this server.

Endpoint
https://mcp.linear.app/mcp
Auth
OAuth
Last checked 2 minutes ago
Example
<Card>
  <CardHeader
    title="Connection"
    description="How agents reach this server."
    actions={<Button size="sm" variant="secondary" icon="refresh">Test</Button>}
  />
  <CardBody>
    <DescriptionList density="compact">…</DescriptionList>
  </CardBody>
  <CardFooter actions={<Button size="sm">Save</Button>}>
    Last checked 2 minutes ago
  </CardFooter>
</Card>

Body only

The simplest card: a surface with padding.

Light

Sentry has 18 tools. Agents see the 6 you enabled.

Dark

Sentry has 18 tools. Agents see the 6 you enabled.

Example
<Card>
  <CardBody>
    <Text size="sm" tone="ink-2">Sentry has 18 tools. Agents see the 6 you enabled.</Text>
  </CardBody>
</Card>

Settings form

Wrap the card in a form so the footer button submits it. Header actions can be any control.

Notion

Pages and databases in the Acme workspace.

Example
<form method="post" action={routes.servers.update.href({ id })}>
  <Card>
    <CardHeader
      title="Notion"
      description="Pages and databases in the Acme workspace."
      actions={<Switch name="enabled" checked aria-label="Enabled" />}
    />
    <CardFooter actions={<Button type="submit" size="sm">Save changes</Button>} />
  </Card>
</form>

Usage

Usage
import { Card, CardHeader, CardBody, CardFooter } from '@switchboard/ui'

<Card>
  <CardHeader title="Tools" description="What agents can call on this server." />
  <CardBody flush>
    <DataTable … />
  </CardBody>
</Card>

Props

Every component also takes mix, for css() and mixins applied after its own style.

Card

Card props
PropTypeDefaultDescription
as'section' | 'div' | 'article' | 'form' | 'li''section'Element to render. Attributes are typed as <section> ones, so wrap the card in a <form> when you need method and action.
mixMix–Extra css() or mixins, applied after the card style.

Also accepts every attribute of the element it renders.

CardHeader

CardHeader props
PropTypeDefaultDescription
titleRemixNode–Card title, rendered as a heading.
descriptionRemixNode–One line under the title.
actionsRemixNode–Right-aligned controls: buttons, a switch, a menu.
level2 | 3 | 42Heading level of the title.

Also accepts every <header> attribute. Children render under the description.

CardBody

CardBody props
PropTypeDefaultDescription
flushboolean–No padding, for tables and lists that run edge to edge.

Also accepts every <div> attribute.

CardFooter

CardFooter props
PropTypeDefaultDescription
actionsRemixNode–Buttons, right-aligned.

Also accepts every <footer> attribute. Children are the hint on the left.

Accessibility

  • Card renders a <section> by default; a section with a heading inside is a named region only if you give it aria-labelledby. Use as="div" when it is just a surface.
  • CardHeader renders a real heading (h2 by default). Set level so it fits the page outline.
  • Inside a <form>, the footer submit button works without JavaScript.

Guidance

Do

  • Put a DataTable in CardBody flush so the table runs to the card edges.
  • Split a long card into header, body and footer parts.

Don’t

  • Nest a Card inside a Card.
  • Put more than one primary button in a card footer.
  • Use a card to wrap a single line of page text; a paragraph will do.