COMPONENTS / MOLECULES / al-card← ALL COMPONENTS

Card

<al-card>betaOPEN IN FIGMA ↗

Southleft's implementation of the card, and a SUPERSET of Altitude's: the same regions (`image`, `header`, `actions-start`, `actions-end`, the default body), the same `layout`, `variant="bare"` and `fill` behaviour, plus the four card treatments southleft.com ships.

PLAYGROUND4 CONTROLS
PREVIEW / al-card / SOUTHLEFT
Loading preview

Design system audits

Where your system is costing the team velocity, what it is costing, and which three things to fix first.

  • Token and theming audit
  • Component inventory and drift report
  • Accessibility baseline
  • A sequenced remediation plan
variant
FLAGS
CODE — HTML

Guidance

AUTHORED · CITED TO SOURCE

Southleft's implementation of the card, and a superset of Altitude's: the same bounded surface with the same named regions, plus the four card treatments this brand ships. service is the "what we do" card — a bordered well on the 40px padding step, a display-face heading with a corner glyph opposite it, and a mono supporting list. tool is the "we publish our homework" card — a mono command line for a title and a footer cue pinned to the bottom edge. article and work are the linked media card — media flush to the card edge, then a padded column carrying a mono meta row, a heading, a clamped excerpt, tag chips and a footer cue.

WHEN TO USE

  • You are building a grid of practice areas, offers or capabilities where each entry has a heading, a short body and a list of what is included. That is variant="service", and the whole card is one link.
  • You are listing things a reader can go and run — packages, repos, CLIs — where the most useful first line is the command itself. That is variant="tool", with the command in the command property.
  • You want the base card and nothing brand-specific. Leave variant unset and this behaves exactly as Altitude's card does, because it is the same component contract.
  • You are listing posts or case studies, each with artwork that should reach the edges of the card. That is variant="article" (or work, the case-study cut with a wider media ratio and a shallower excerpt clamp).
  • A row of cards should share one height and line their footer cues up. Set fill; under tool, article and work the footer is pinned to the bottom edge, so the cues line up however long each description runs.

WHEN NOT TO USE

  • The subject is a logo, and what you want is a wall of them at a shared optical weight. That is a different problem — grid rhythm and greyscale treatment, not a bounded surface per subject.USE LOGO-WALL →
  • You only need to arrange cards with spacing between them. Arrangement is never a card's job — the grid the homepage lays these out in is the layout primitive, not anything declared here.USE LAYOUT →
  • What you are showing is one number with a label and a trend. That has its own component with the typography and the delta treatment already decided.USE STAT →
  • The content is a quotation with an attribution. A card gives it a surface but none of the semantics a quote needs.USE TESTIMONIAL →

DO

  • Use href rather than wrapping the card in your own <a>. It makes the whole surface one link target with one focus ring, which is what every brand variant is — a card wrapped from outside gives you a link around a link.
  • Let article and work pick their own heading scale. Left unset, heading-variant resolves to lg for a work card or a featured article and md otherwise, which is the judgment the consuming site used to make inline at every call site.
  • Add the accent bullets for a service card's list in your own stylesheet, scoped to al-card[variant='service'] ul[slot='list'] li. The component cannot do it and the reason is structural, not an oversight.
  • Set command-prefix as a kebab-case attribute, not commandPrefix. HTML lowercases attribute names, so the camelCase spelling never reaches the property and the prefix silently falls back to $.
  • Use fill rather than an inline height: 100%. :host is display: contents, so a height written on <al-card> from outside is dropped entirely.

DON’T

  • Don't put a button inside a service, tool, article or work card. Each is a single link in its entirety, and a nested interactive region leaves a keyboard user with no way to tell which target they are about to activate.
  • Don't import the base al-card module alongside this one. Both packages publish the same tag, customElements.define is first-come and final, and the loser's markup renders into an element that never upgrades.
  • Don't reach for a brand variant just to get a border and some padding. Each carries the brand's display face, its mono face and its own rhythm — use the default variant, which is the plain surface.
  • Don't expect the image SLOT to bleed to the edge. It sits inside the card's padding exactly as Altitude's does, because three example apps put an <al-avatar> there. Edge-to-edge media is what the image PROPERTY plus variant="article" or work gives you.
  • Don't slot a second heading into the cue slot. It is a decorative glyph position, sized and coloured as chrome.

ACCESSIBILITY IN PRACTICE

  • The corner glyph is decoration on a surface that is already a link. Mark it aria-hidden="true", or a screen reader announces an arrow character in the middle of the link's name.
  • Put a real heading element in the header slot, or use the heading property, which renders an <al-heading tagName="h3">. The card's container carries no heading semantics of its own, so one of the two is what gives a screen-reader user a way to navigate between cards.
  • When target="_blank" is set the component adds rel="noopener" for you, so an external card cannot ship without it by omission.
  • A card whose whole surface is the link needs a link name that survives being read out of context. Ten cards that each resolve to "↗" are ten identical entries in a list of links.

CONTENT GUIDELINES

  • A tool card's command is the headline. Write the command a reader would actually type, not a description of it — the $ prefix is chrome and is hidden from assistive technology.
  • Keep a service card's list to four items or fewer. It is set in 12px mono and read at a glance in a grid, not studied.
  • Lead with what distinguishes this card from the one beside it. The words every card in the row shares are the least useful ones in the first line.
  • Keep an excerpt within its clamp — three lines under article, two under work. Anything past it is truncated with an ellipsis, so a sentence that carries its point at the end loses it.

WHERE THIS CAME FROM

  • libs/sl-web-components/components/card/card.tsThe contract this guidance rests on — every region and property of the base card, plus the two brand variants.
  • libs/sl-web-components/components/card/card.tsWhy the base card module must not be imported alongside this one, and why this component re-implements rather than extends.
  • libs/sl-web-components/components/card/card.tsWhy the media treatment is a variant here rather than the separate component it used to be — this card owns its own padding.
  • libs/sl-web-components/components/card/card.tsThe default applied whenever the link opens in a new tab, which the accessibility note depends on.
  • libs/sl-web-components/components/card/card.scssWhy the accent list bullet is the consumer's one line of CSS rather than something the component owns.
  • apps/southleft/src/styles/layout.cssThe consuming site's record of the same boundary — the one node inside a slotted row it still has to colour itself.
  • AGENTS.mdThe rule that keeps arrangement out of content components like this one.

Each line above names a file and a literal string that must still appear in it.pnpm gate:guidance re-reads them from this built page, so guidance that has stopped being true fails a check rather than misleading you.

FIGMA AHEADSouthleft Design SystemFIGMA SET · Card

The Figma set changed since the last confirmed sync — the code is behind.

DECIDED BY
CONTRACT DIFF
FIGMA SIDE
OBSERVED
LAST CONFIRMED SYNC
2026-08-28
PUBLIC SURFACE
17 ATTR · 11 SLOT · 0 EVENT · 7 PART

The component’s public surface — attributes and their value sets, slots, events, CSS parts and custom properties, from the manifest — compared against the Figma set’s property definitions.

PROPERTYDIFFERENCECODE / FIGMA
variantThe two sides offer a different number of variant values.5 / 1

The axe run measures libs/al-web-components's Storybook. This component ships in @southleft/sl-web-components — a separate package with no Storybook in the measured set — so axe has not been pointed at it yet. It is not that the component lacks stories: the preview above is rendered from them.

import '@southleft/sl-web-components/components/card';
PROPTYPEDEFAULT
layout'inline'Layout - **default** Displays the slotted items stacked in a column - **row** Displays the slotted items inline in a row
variant'bare' | 'service' | 'tool' | 'article' | 'work'Variant - **default** A card with a background color, box shadow and border radius - **bare** No background color, box shadow or border radius - **service** The brand's "what we do" card: a bordered well, the 40px padding step, a display-face heading and a mono list - **tool** The brand's "we publish our homework" card: a mono command line and a footer cue pinned to the bottom - **article** The linked media card: flush-to-edge media, a meta row, a heading, a clamped excerpt and a mono footer cue - **work** The case-study cut of the media card: a wider media ratio, a two-line clamp and a tighter rhythm
fillbooleanFill the available block size instead of hugging the content. Reflected, so a page can also select `al-card[fill]`. It has to be a property rather than something the page sets from outside: `:host` is `display: contents`, so a `height: 100%` written on `<al-card>` is dropped entirely.
hrefstringMake the whole card one link target. The brand's `service`, `tool`, `article` and `work` cards are links in their entirety, which is why this is a property and not a slotted `<a>`: one target, one focus ring, and no nested interactive regions for a keyboard user to get lost between. Do not combine it with buttons inside the card.
targetstringLink target, when `href` is set. `_blank` for the off-site tool cards.
relstringLink `rel`. Defaults to `noopener` whenever `target="_blank"`, so an external card cannot be opened without it by forgetting to pass it.
commandstringThe mono command line that titles a `tool` card — `npx altitude init`. A property rather than a slot because the prefix glyph is styled separately from the command, and `::slotted()` reaches direct children only: a slotted `<p><span>$</span> npx …</p>` puts the span out of reach.
command-prefixstringThe muted glyph before `command`. `$` for a shell line, `//` for a note.'$'
dashedbooleanDashed rather than solid border — the brand's "coming soon" tool card.
imagestringMedia source for the `article` / `work` variants. Omit to render `fallback`.
image-altstringAlt text. Empty by default: the heading already names the destination, so the image is decorative.''
fallbackstringStand-in shown when there is no image — the brand uses `<P>`, the client initial in angle brackets.
headingstringThe card's heading. Rendered as an `<al-heading>`, in place of the `header` slot.
excerptstringThe clamped summary under the heading.
footer-labelstringThe mono cue in the footer, e.g. `read →`. Omit for no footer.
featuredbooleanThe larger treatment used for a lead card that spans two grid columns.
heading-variantstringHeading scale override. Left unset, it follows the anatomy: `lg` for work cards and featured articles (short client names and lead treatments), `md` for a standard article card, whose long sentence titles overwrap badly at `lg`. Altitude's scoped steps here are 24px and 48px with nothing between — a real gap in `al-heading`, documented rather than papered over.
SLOTDESCRIPTION
(default)The card's body.
imageMedia above the header. Under the base variants it sits INSIDE the card's padding, exactly as Altitude's card does; under `article`/`work` the media region is flush to the card edge. The `image` property is the usual way to fill it for the media variants; this slot is for anything else, an `<al-avatar>` included.
headerThe card's heading row. Ignored when the `heading` property is set, which is how the media variants supply theirs.
action-rightTop-right single control (kebab / overflow menu). Altitude's card documents this slot but renders no matching `<slot>` element, so anything assigned to it there disappears; this implementation actually renders it, in the header row's trailing cluster beside `cue`.
cueA trailing cue on the header row — the corner glyph under `variant="service"`. Decorative; mark it `aria-hidden` when the card is already a link.
metaA mono meta row above the heading — category, date. Each slotted node is its own item in a row. Collapses entirely when empty.
tagsTag chips under the body. Each slotted node gets the brand's square-cornered chip treatment, so plain `<span>`s are enough. Collapses entirely when empty.
listA supporting list below the body. Under `variant="service"` a slotted `<ul>` gets the brand's mono treatment.
footerA footer cue pinned to the bottom of the card, whatever the body's height. Ignored when the `footer-label` property is set.
actions-startTrailing-action row, leading edge.
actions-endTrailing-action row, trailing edge — the canonical primary action.

THIS COMPONENT DECLARES NO EVENTS IN THE MANIFEST.

PARTDESCRIPTION
cardThe card surface itself. An `<a>` when `href` is set, a `<div>` otherwise.
imageThe media region.
bodyThe padded content column.
headingThe `<al-heading>` rendered from the `heading` property.
excerptThe clamped excerpt paragraph.
footerThe mono footer cue.
meta
PROPERTYDESCRIPTIONDEFAULT
--al-card-paddingThe card's padding. Defaults to `--al-theme-space`; the brand's 40px step under `service`, `--al-theme-space-xl` under `tool`, and under `article`/`work` it moves off the surface and onto the content column.
--al-card-media-aspect-ratioThe media region's ratio under `article`/`work`. Defaults to `16 / 9`, or `16 / 10` under `work`.
--al-card-line-clampLines the excerpt is clamped to. Defaults to `3`, or `2` under `work`.

SOURCE — components/card/card.ts · Molecules/Card