Tessina UI
ConfiguratorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleCheckboxChipCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFile Upload 2FlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

Card

A flexible surface container for grouping related content. Four variants (elevated/outlined/filled/ghost), five intents, six rounded options, five sizes, horizontal layout, interactive states, and LTR/RTL support.

Playground

Installation

pnpm add @tessinaui/ui

Usage

import {
  Card,
  CardMedia,
  CardHeader,
  CardTitle,
  CardDescription,
  CardAction,
  CardContent,
  CardFooter,
  CardDivider,
} from "@tessinaui/ui";
{/* Basic card */}
<Card>
  <CardHeader>
    <CardTitle>Title</CardTitle>
    <CardDescription>Description</CardDescription>
  </CardHeader>
  <CardContent>Content</CardContent>
  <CardFooter>
    <Button variant="primary">Action</Button>
  </CardFooter>
</Card>

{/* Outlined with intent */}
<Card variant="outlined" intent="success">
  <CardHeader>
    <CardTitle>Success</CardTitle>
  </CardHeader>
</Card>

{/* Card with media */}
<Card rounded="lg" className="overflow-hidden w-72">
  <CardMedia src="/image.jpg" alt="Cover" aspectRatio="video" position="top" />
  <CardHeader>
    <CardTitle>Article title</CardTitle>
  </CardHeader>
</Card>

{/* Interactive card */}
<Card interactive onClick={() => navigate('/details')}>
  <CardContent>Click me</CardContent>
</Card>

{/* Horizontal layout — CardBody supplies the column's vertical rhythm.
    Start/end media width defaults to the size-scaled --card-media-w
    (capped at 40% so the text column never starves); override with any
    w-* class. */}
<Card horizontal className="max-w-lg overflow-hidden">
  <CardMedia position="start" className="bg-secondary" />
  <CardBody>
    <CardHeader>
      <CardTitle>Horizontal card</CardTitle>
    </CardHeader>
    <CardContent>Content beside media</CardContent>
  </CardBody>
</Card>

{/* RTL */}
<Card dir="rtl" variant="outlined">
  <CardHeader>
    <CardTitle>عنوان البطاقة</CardTitle>
  </CardHeader>
</Card>

Header action

CardAction claims the trailing slot of the header grid — top-aligned with the title, flipped to the start edge in RTL. Use it for link buttons, badges, or a dismiss IconButton.

<Card>
  <CardHeader>
    <CardTitle>Team members</CardTitle>
    <CardDescription>Invite and manage collaborators.</CardDescription>
    <CardAction>
      <Button variant="ghost" size="xs">View all</Button>
    </CardAction>
  </CardHeader>
  <CardContent>…</CardContent>
</Card>

Edge-to-edge content

The root owns the spacing model — it sets --card-p (edge padding) and --card-gap (section rhythm) per size, while sections pad horizontally only — so any child can bleed to the card edge with -mx-(--card-p) at every size.

<Card>
  <CardHeader>
    <CardTitle>Monthly report</CardTitle>
  </CardHeader>
  <CardContent>
    <div className="-mx-(--card-p) border-y border-border">
      {/* full-bleed table, chart, or list rows */}
    </div>
  </CardContent>
</Card>

Examples

Default

A basic card with header, content, and footer sections.

Variants

Four surface styles — elevated, outlined, filled, and ghost.

Intents

Semantic intent colors applied to the card surface.

Sizes

The size prop scales the card's --card-p / --card-gap spacing rhythm.

With Media

A CardMedia block rounds its top corners to sit flush with the card edge.

Loading

Use CardSkeleton to show a placeholder while content loads.

API Reference

Card Props

PropTypeDefaultDescription
variant"elevated" | "outlined" | "filled" | "ghost""elevated"Visual surface style
intent"none" | "error" | "warning" | "success" | "info""none"Semantic intent color applied to the card surface
size"xs" | "sm" | "md" | "lg" | "xl""md"Spacing scale — sets the card's --card-p / --card-gap rhythm
rounded"none" | "sm" | "md" | "lg" | "full""md"Corner radius
width"narrow" | "default" | "wide" | "full""default"Max-width constraint
shadow"none" | "sm" | "md" | "lg""md" for elevated, "none" otherwiseDrop shadow depth
interactivebooleanfalseAdds hover / active / focus-ring states
disabledbooleanfalseDims the card and disables pointer events
horizontalbooleanfalseSwitches internal flex direction to row
dir"ltr" | "rtl""ltr"Text direction
classNamestring—Additional class on the root element

CardMedia Props

PropTypeDefaultDescription
srcstring—Image source URL
altstring""Image alt text
aspectRatio"auto" | "video" | "4/3" | "1/1" | "2/1" | "3/4""video"Aspect ratio of the media container
position"top" | "bottom" | "start" | "end" | "fill""top"Edge the media abuts — determines which corners are rounded
overlay"none" | "scrim" | "gradient""none"Legibility layer over the image — scrim darkens uniformly, gradient fades up from the bottom (caption-friendly)
childrenReactNode—With src: floated over the image in the top-end corner (badges, chips). Without src: replaces the default <img> (video, iframe, custom media)

CardHeader Props

PropTypeDefaultDescription
iconReactNode—Icon / illustration rendered above (or inline with) the title
inlineIconbooleanfalseLays out icon and title text in a horizontal row

CardAction

A plain div with no extra props — the trailing header slot. Render it inside CardHeader alongside CardTitle / CardDescription; the header grid places it in the end column, top-aligned with the title. Logical grid placement means it lands on the left automatically in RTL.

CardBody

A plain div with no extra props — the content column of a horizontal card (flex min-w-0 flex-1 flex-col gap-(--card-gap)). Wrap CardHeader / CardContent / CardFooter in it next to a start/end CardMedia; it reuses the inherited --card-gap so the column's rhythm matches a vertical card exactly.

CardFooter Props

PropTypeDefaultDescription
verticalbooleanfalseStacks footer items vertically
align"start" | "center" | "end" | "between""start"Alignment of footer items

Variants

ValueStyle
"elevated"bg-card with drop shadow (default shadow-md)
"outlined"bg-background with border-border — flat with visible border
"filled"bg-secondary (or intent-light color) — tinted surface
"ghost"Transparent — no border, no shadow

Notes

  • Spacing model — the root owns two CSS variables per size: --card-p (edge padding, applied as the root's vertical padding) and --card-gap (the flex gap between sections). Sections apply horizontal padding only (px-(--card-p)), so the space between any two sections is always exactly --card-gap — no double padding. Any child can bleed edge-to-edge with -mx-(--card-p), and CardMedia cancels the root's vertical padding the same way. lg / xl automatically tighten --card-p on mobile (max-md) so large cards stay comfortable on phones.
  • Machine-readable anatomy — every part exposes a data-slot attribute (card, card-media, card-header, card-title, card-description, card-action, card-content, card-footer, card-divider) for styling hooks and tooling.
  • Accessible naming — interactive cards get role="button" and are automatically labelled by their CardTitle via aria-labelledby, so assistive tech announces the card by its visible title.
  • CardMedia rounding — use position to control which corners are rounded on the media figure. Set position="start" for the left edge of horizontal cards; it uses logical CSS properties so it flips automatically in RTL.
  • Interactive cards — when interactive is set and the card contains nested buttons or links, prefer the stretched-link pattern (absolute-positioned <a>) rather than onClick on the card root to avoid nested interactive element accessibility issues.
  • Horizontal layout — set horizontal on the Card root, then use CardMedia with position="start" (or "end") and wrap the remaining sections in CardBody, which restores the card's vertical rhythm inside the column (the root's gap only separates its direct children). Start/end media stretches the card's full height (aspectRatio="auto" default) and its width defaults to the size-scaled --card-media-w (5–12rem from xs to xl, capped at 40% of the card) so it grows proportionally with size — override with any w-* class.
  • Width — narrow and wide include w-full with a max-w cap, so they engage even when the card is a flex item; default sizes to context.
  • RTL — dir="rtl" is applied to the root and propagates to all children. Logical CSS properties (rounded-s-*, rounded-e-*) ensure media rounding flips correctly.

Calendar

Date picker calendar built on react-day-picker v9. Supports single, range, and multiple selection modes, RTL, week numbers, disabled dates, multi-month view, and an optional footer slot.

Carousel

Touch-friendly slider with multiple variants — slide, card/3D, fade, and free-scroll. Built on Embla Carousel with autoplay, loop, RTL, and vertical orientation support.

On this page

PlaygroundInstallationUsageHeader actionEdge-to-edge contentExamplesDefaultVariantsIntentsSizesWith MediaLoadingAPI ReferenceCard PropsCardMedia PropsCardHeader PropsCardActionCardBodyCardFooter PropsVariantsNotes