Card and Surface
import { Card, Surface } from '@absoluteui/components/card';import '@absoluteui/components/card/style.css';Using Angular? See Card and Surface for Angular.
What it does
Section titled “What it does”Card and Surface. .aui-surface is a bordered panel on color-surface, with flat, raised, and sunken variants and optional inset padding. .aui-card adds header (title, subtitle), media, body, and footer sections. Both work as CSS-only APIs.
Card.create renders the structure with the right semantics:
- an
<article>named by its title when there is one, otherwise a plain<div>; - an optional block-link mode, where the whole card is clickable, there is exactly one tab stop, and screen readers announce only the title.
When to use
Section titled “When to use”- Grouping a self-contained piece of content: a product, project, article teaser, or summary tile.
- Collections of similar items in a grid, each linking to a detail page (interactive cards).
- Panels that visually separate a region of a page (Surface).
When not to use
Section titled “When not to use”- A single clickable action without content: use a Button or Link.
- Cards with several equally important actions: do not use interactive mode; put actions in the footer instead.
- Tabular or comparable data across items: use a table.
- Nesting cards inside cards.
Examples
Section titled “Examples”Titled card with footer actions
Section titled “Titled card with footer actions”import { Card } from '@absoluteui/components/card';import { Button } from '@absoluteui/components/button';
const host = document.createElement('div');document.body.append(host);const archive = document.createElement('button');archive.type = 'button';archive.textContent = 'Archive';Button.enhance(archive);Card.create(host, { title: 'Quarterly report', subtitle: 'Updated today', content: 'Revenue grew 4% compared to last quarter.', footer: archive, variant: 'raised',});Whole-card link (block-link pattern)
Section titled “Whole-card link (block-link pattern)”import { Card } from '@absoluteui/components/card';
const host = document.createElement('div');document.body.append(host);const img = document.createElement('img');img.src = '/images/alpha.png';img.alt = '';Card.create(host, { title: 'Project Alpha', subtitle: 'Owner: Ada Lovelace', media: img, interactive: { href: '/projects/alpha' }, headingLevel: 2,});Padded sunken panel
Section titled “Padded sunken panel”import { Surface } from '@absoluteui/components/card';
const panel = document.createElement('section');document.body.append(panel);const surface = Surface.apply(panel, { variant: 'sunken', padded: true });surface.update({ variant: 'raised' });Card titles are h2 on this page
Section titled “Card titles are h2 on this page”import { configureAbsoluteUI } from '@absoluteui/core';
configureAbsoluteUI({ components: { card: { headingLevel: 2 } } });import { Card } from '@absoluteui/components/card';import '@absoluteui/components/card/style.css';
const host = document.querySelector<HTMLElement>('#report');if (host) { const button = document.createElement('button'); button.type = 'button'; button.textContent = 'Download'; const card = Card.create(host, { title: 'Quarterly report', content: 'Revenue grew 4%.', footer: button, variant: 'raised' }); button.addEventListener('click', () => { card.update({ subtitle: 'Download started' }); });}More usage
Section titled “More usage”<article class="aui-surface aui-card" aria-labelledby="c1"> <div class="aui-card__header"><h3 class="aui-card__title" id="c1">Quarterly report</h3></div> <div class="aui-card__body">Revenue grew 4%.</div></article>import { Card } from '@absoluteui/components/card';
const host = document.createElement('div');document.body.append(host);Card.create(host, { title: 'Project Alpha', subtitle: 'Owner: Ada', interactive: { href: '/projects/alpha' } });Anatomy
Section titled “Anatomy”<div> (consumer host) <article class="aui-surface aui-card [aui-surface--raised|--sunken] [aui-card--interactive]" aria-labelledby=title> <div class="aui-card__header"> <h3 class="aui-card__title" id=title>Title (h2-h6, headingLevel) <a class="aui-card__link" href>Title</a> (interactive only; ::after covers the card) </h3> <p class="aui-card__subtitle">Subtitle</p> (optional) </div> <div class="aui-card__media"> (visually first; hidden while empty) <div class="aui-card__body"> (content; hidden while empty) <div class="aui-card__footer"> (actions/metadata; hidden while empty) </article> (<div> when there is no title)</div>API reference
Section titled “API reference”Factory
Section titled “Factory”Card.create(host: HTMLElement, options?: CardOptions): Card; Surface.apply(el: HTMLElement, options?: SurfaceOptions): SurfaceHandleGlobal configuration key: card (see Configuration).
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
title |
string |
Card heading and the <article>’s accessible name (aria-labelledby). Without a title the card is a <div>. | |
subtitle |
string |
Supporting line under the title (text only). | |
content |
Node | string |
Body content; strings render as text. Omit to fill card.body directly. | |
footer |
Node |
Footer content such as actions or metadata. | |
media |
Node |
Image or illustration, shown above the header while the header stays first in source order. | |
interactive |
{ href: string } |
Block-link mode: the title becomes the only link and its ::after covers the card. Requires title; href must be http(s) or relative. | |
variant |
'flat' | 'raised' | 'sunken' |
"flat" |
Surface treatment (Card and Surface.apply). |
padded |
boolean |
false |
Surface.apply only: space-inset padding. |
headingLevel |
2 | 3 | 4 | 5 | 6 |
3 |
Heading level of the card title; match it to the surrounding outline. Globally configurable. |
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
host |
HTMLElement |
Card: consumer host; its children are replaced by the card. |
element |
HTMLElement |
Card: root <article> (titled) or <div>. Replaced when the title is added or removed. |
body |
HTMLDivElement |
Card: body content area. |
footer |
HTMLDivElement |
Card: footer area (hidden while empty). |
media |
HTMLDivElement |
Card: media area (hidden while empty). |
options |
Readonly<CardOptions> | Readonly<SurfaceOptions> |
Current options. |
Methods
Section titled “Methods”| Method | Description |
|---|---|
update(options: Partial<CardOptions>): void |
Merge, validate, and apply options. Content areas are replaced only when their option is passed; pass undefined to remove title, subtitle, or interactive. |
destroy(): void |
Card: remove the card element and the configuration subscription. SurfaceHandle: remove the classes it added. |
Content areas
Section titled “Content areas”| Area | Description |
|---|---|
body |
card.body (or the content option). |
footer |
card.footer (or the footer option). |
media |
card.media (or the media option). |
CSS classes
Section titled “CSS classes”All rules live in the aui.components cascade layer, so application styles override them without specificity battles.
| Class | Purpose |
|---|---|
aui-surface |
Bordered panel on color-surface with radius-md. |
aui-surface--raised | --sunken | --padded |
Surface modifiers. |
aui-card |
Card root (combined with aui-surface). |
aui-card--interactive |
Block-link card: hover and focus-visible states on the card. |
aui-card__header |
Title and subtitle. |
aui-card__title |
Heading (h2 to h6). |
aui-card__link |
Interactive title link; its ::after covers the card. |
aui-card__subtitle |
Supporting text. |
aui-card__media |
Media area, visually first. |
aui-card__body |
Body content. |
aui-card__footer |
Footer, a wrapping row. |
Design tokens
Section titled “Design tokens”Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.
--aui-border-width | --aui-color-border | --aui-color-border-strong | --aui-color-focus-ring | --aui-color-link | --aui-color-surface | --aui-color-surface-sunken | --aui-color-text | --aui-color-text-muted | --aui-duration-fast | --aui-easing-standard | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-lg | --aui-font-size-md | --aui-font-size-sm | --aui-font-weight-semibold | --aui-line-height-normal | --aui-line-height-tight | --aui-radius-md | --aui-shadow-md | --aui-shadow-sm | --aui-space-1 | --aui-space-2 | --aui-space-inset
TypeScript definitions
Section titled “TypeScript definitions”type CardHeadingLevel = 2 | 3 | 4 | 5 | 6;type SurfaceVariant = 'flat' | 'raised' | 'sunken';interface CardInteractive { href: string }interface CardOptions { title?: string; subtitle?: string; content?: Node | string; footer?: Node; media?: Node; interactive?: CardInteractive; variant?: SurfaceVariant; headingLevel?: CardHeadingLevel;}interface SurfaceOptions { variant?: SurfaceVariant; padded?: boolean }interface SurfaceHandle { readonly element: HTMLElement; readonly options: Readonly<SurfaceOptions>; update(o: Partial<SurfaceOptions>): void; destroy(): void }Accessibility
Section titled “Accessibility”Pattern: HTML <article> named by its heading; interactive cards use the block-link pattern (Inclusive Components ‘Cards’): one real link on the title, stretched over the card with a pseudo-element.
Keyboard
Section titled “Keyboard”| Key | Context | Action |
|---|---|---|
| Tab / Shift+Tab | Interactive card | One tab stop: the title link. Other controls inside the card (for example footer buttons) are separate tab stops after it. |
| Enter | Title link focused | Follows the link (native). |
| Tab / Shift+Tab | Non-interactive card | The card itself is not focusable; focus moves through any controls in its content. |
| Element | Role |
|---|---|
| titled card root | article (native <article>) |
| untitled card root | none (generic <div>) |
| title | heading, level 2 to 6 (headingLevel, default 3) |
| interactive title | link (native <a href>) inside the heading |
ARIA attributes
Section titled “ARIA attributes”| Attribute | Element | Value |
|---|---|---|
aria-labelledby |
<article> | id of the title heading |
Interactive cards draw the focus indicator on the whole card (:has(.aui-card__link:focus-visible)) and underline the title; the link’s own outline is suppressed so only one indicator shows.
WCAG 2.2 success criteria addressed
Section titled “WCAG 2.2 success criteria addressed”| Criterion | Level | Note |
|---|---|---|
| 1.3.1 Info and Relationships | A | |
| 1.4.1 Use of Color | A | |
| 1.4.3 Contrast (Minimum) | AA | |
| 1.4.11 Non-text Contrast | AA | |
| 2.1.1 Keyboard | A | |
| 2.4.4 Link Purpose (In Context) | A | |
| 2.4.7 Focus Visible | AA | |
| 2.5.3 Label in Name | A | |
| 4.1.2 Name, Role, Value | A |
Automated test results: Accessibility and compliance.
Notes and known caveats
Section titled “Notes and known caveats”- Screen readers announce only the title as the link name, not the whole card content.
- The media area is visually first but after the header in source order, so the heading is reached first when navigating by heading or reading.
- Section containers are <div>s, not <header>/<footer>, so untitled cards never create banner or contentinfo landmarks.
- Consumers must give informative images in media a text alternative, or alt=“” when decorative.
- Mouse text selection inside interactive cards is limited by the link overlay.
- Manual screen-reader verification (NVDA, JAWS, VoiceOver) is pending.
Behavior
Section titled “Behavior”States
Section titled “States”default, raised, sunken, interactive, hover (interactive), focus-visible (interactive).
State transitions
Section titled “State transitions”| From | Trigger | To |
|---|---|---|
untitled <div> |
update({ title }) |
<article>; content areas move to the new root |
| titled | update({ title: undefined }) |
<div>; interactive must be removed first or in the same update |
| static | update({ interactive: { href } }) |
block link |
| any | global headingLevel change |
title heading re-created at the new level |
Accessibility notes
Section titled “Accessibility notes”- Block-link pattern: wrapping the whole card in
<a>would make screen readers read the entire card as the link name. Here only the title is the link, stretched with::after. - The focus indicator is drawn around the whole card (
:has(.aui-card__link:focus-visible)). - Media is visually first but comes after the header in source order, so heading navigation reaches the title first.
- Give informative images a text alternative, or
alt=""when decorative. - Manual screen-reader verification is pending.
Responsive behavior
Section titled “Responsive behavior”Cards fill their container. For responsive collections, place them in an aui-grid aui-grid--auto (layout). Footer content wraps; media images are fluid.
Right-to-left
Section titled “Right-to-left”Logical properties throughout; all sections follow the writing direction.
Theming
Section titled “Theming”Colors, radius, shadow, and padding come from role tokens (space-inset follows density). Verified token pairs: color-text and color-text-muted on color-surface and color-surface-sunken, and color-link on color-surface. In forced-colors mode, the border stays, the title link uses LinkText, and focus uses a Highlight outline.
Global configuration
Section titled “Global configuration”configureAbsoluteUI({ components: { card: { headingLevel: 2 } } });Existing cards re-render their title at the new level.
Component configuration
Section titled “Component configuration”The headingLevel option overrides global and theme defaults. All options are dynamic.
None. Cards render no icons of their own.
Security
Section titled “Security”- Title, subtitle, and string content use
textContent.Nodecontent is appended as-is; the consumer owns it. interactive.hrefis checked with the WHATWG URL parser and must be http(s) or relative.javascript:,data:, and other schemes are rejected, including obfuscated forms such asjava\tscript:.- No inline styles. CSP-safe.
Data handling
Section titled “Data handling”No persistence, network access, or telemetry.
Performance
Section titled “Performance”Constant DOM per card (root plus five section elements), no event listeners, and one configuration subscription. There is no dedicated performance budget yet.
Supported data scale
Section titled “Supported data scale”Not data-driven. For long card collections, render only visible cards (virtualize) in the application.
Edge cases
Section titled “Edge cases”interactivewithouttitlethrowsCARD_OPTIONS.- Adding or removing the title replaces
card.element(the tag changes).body,footer, andmediakeep their content. - Controls inside an interactive card are lifted above the overlay (
position: relative; z-index: 1), so they stay clickable. update()withoutcontentkeeps whatever the consumer appended tocard.body.
Errors
Section titled “Errors”| Code | Category | Cause |
|---|---|---|
CARD_OPTIONS |
configuration | Blank title, invalid content types, invalid variant, interactive without a title, or an unsafe href. |
SURFACE_OPTIONS |
configuration | Invalid surface variant or padded. |
SURFACE_ALREADY_APPLIED |
configuration | Surface.apply on an element that already has an active surface handle. |
CFG_INVALID_VALUE |
configuration | headingLevel outside 2-6. |
A failed construction leaves the host untouched and registers no subscription.
Known limitations
Section titled “Known limitations”- Mouse text selection inside interactive cards is limited by the link overlay.
- The root element is replaced when the title is added or removed.
- Manual screen-reader verification is pending.
Unsupported scenarios
Section titled “Unsupported scenarios”- HTML strings as title, subtitle, or content (pass a
Node). - A button (non-navigation action) as the whole-card target.
- Heading level 1 for card titles.