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.
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
CODE — HTML
Guidance
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 thecommandproperty. - You want the base card and nothing brand-specific. Leave
variantunset 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"(orwork, 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; undertool,articleandworkthe 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
hrefrather 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
articleandworkpick their own heading scale. Left unset,heading-variantresolves tolgfor a work card or a featured article andmdotherwise, which is the judgment the consuming site used to make inline at every call site. - Add the accent
›bullets for aservicecard's list in your own stylesheet, scoped toal-card[variant='service'] ul[slot='list'] li. The component cannot do it and the reason is structural, not an oversight. - Set
command-prefixas a kebab-case attribute, notcommandPrefix. HTML lowercases attribute names, so the camelCase spelling never reaches the property and the prefix silently falls back to$. - Use
fillrather than an inlineheight: 100%.:hostisdisplay: contents, so a height written on<al-card>from outside is dropped entirely.
DON’T
- Don't put a button inside a
service,tool,articleorworkcard. 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-cardmodule alongside this one. Both packages publish the same tag,customElements.defineis 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
imageSLOT 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 theimagePROPERTY plusvariant="article"orworkgives you. - Don't slot a second heading into the
cueslot. 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
headerslot, or use theheadingproperty, 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 addsrel="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
toolcard'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
servicecard'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 underwork. 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 PARITY
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.
ACCESSIBILITY
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.
INSTALL
import '@southleft/sl-web-components/components/card';
API — 17 PROPS
SLOTS
EVENTS
THIS COMPONENT DECLARES NO EVENTS IN THE MANIFEST.
CSS PARTS
CSS CUSTOM PROPERTIES
SOURCE — components/card/card.ts · Molecules/Card