Skip to contentAsheeUIAsheeUI

Card

A boundary around one subject, picture, heading, context and the actions that belong to them.

A card is a boundary: it groups one subject's picture, heading, sentence of context and actions on a surface that says these belong together, so a page can show several of them without inventing a layout each time.

It is not a layout primitive, the arrangement inside it comes from Stack and Grid, and the card supplies the surface, the spacing scale and, when it is interactive, the semantics. Nor is it a container for other cards: the surface is drawn once, by the outer one. Its siblings state one fact each: Badge and Chip label, Avatar represents, and a card is where all of them are put.

Usage

import { Card } from "@asheeui/web";

<Card title="Invoice INV-1041" description="Northwind Traders · due 31 March">
  £4,280.00 outstanding across twelve line items.
</Card>

children is the body, and body states it explicitly when the card's content is not the element's children. header sits above the title, title and description are rendered for you, and footer sits at the bottom of the content column, which is where a price and its actions usually go.

Pictures

An image has three placements. top and bottom draw it outside the padded content, so it is full-bleed and clipped by the card's radius; background draws it under the content instead, for a card that is a cover rather than a row.

In the image object, ratio locks the frame the picture is fitted into and fit decides how it fills that frame, video and cover when nothing states otherwise, and both resolve through the cascade, so components.card.image.ratio retunes every card at once. loading resolves the same way and falls back to lazy. loading and priority that the picture's own component states are forwarded as given: the card does not overrule a framework's loader.

image.component and image.componentProps keep a framework's own image working inside the card, the intrinsic size, the loader, the allowed host, and link.component does the same for the element the whole card renders as. Both are the framework's one seam: links and substitution.

Cards that go somewhere

A card becomes interactive when link or onClick is given, or when isClickable says so. A link makes the root the link itself rather than a handler on a div, so it is announced as a link and opens like one, and link.component renders a router's own link in its place. An onClick without a link keeps the card a div but gives it button semantics, so Enter and Space both work.

link.componentProps is spread onto the card's root last, so its href and its own onClick win over the props the framework passes, and the card's onClick still runs beside the link's. className is the one exception: it is merged into the card's classes through cn rather than spread last, because spreading it last would replace the surface the card drew. isDisabled dims the card, refuses the press and takes it out of the tab order: an unavailable card is drawn but cannot be reached, the opposite of a disabled Button, which stays focusable so a reader can find out why it will not press. A card that renders a real link stays reachable even when isClickable is false, the card's own affordance and the link are separate things, and a destination should not become unreachable because a prop described the surface.

Props

size, animate and image.position are registered as md, true and top, so density, the press animation and the default picture placement resolve from the instance or from components.card. variant and radius are not registered: radius resolves from the application's defaultRadius, the framework's own is xs, so a card is only slightly rounded until the product rounds more, and variant resolves from the application's defaultVariant through a map onto the four surfaces a card draws, so solid becomes elevated and faded or underlined land on the resting bordered. A page with no configuration gets a bordered card on the application's radius.

image.ratio and image.fit resolve from the instance, then components.card.image, then the video and cover fallbacks; image.loading resolves the same way. titleAs changes the heading level and is instance-only. link, isClickable, isDisabled, the four content slots and the image's src, alt and component are instance-only too, because they state this card's subject rather than the design's.

Accessibility

  • title is rendered as a level-three heading by default, so a card belongs under a level-two heading; titleAs states the level when the card sits somewhere else in the outline.
  • A card with onClick renders role="button" and answers Enter and Space. A card with a link renders the link element instead and keeps that element's own activation, so it is announced as a link, opens in a new tab, and its destination can be copied.
  • isDisabled sets aria-disabled, dims the card, removes it from the pointer (pointer-events-none) and from the tab order (tabindex="-1").
  • The picture is the only part with alternative text: state image.alt, or pass an empty one for a decorative cover whose title already says what it is.