Playground
A surface with optional media, heading, body and footer. Sections that get no content collapse (whitespace doesn't count), and href makes the whole card one link: its heading, stretched over the card.
Examples
Media, body and footer
<sb-card heading="Planet X-9" style="inline-size: 18rem">
<img slot="media" src="/art/landscape.svg" alt="A violet planet rising over pixel hills" width="256" height="144">
A cold, quiet world with excellent stargazing.
<sb-button slot="footer" size="sm">Visit</sb-button>
<sb-button slot="footer" size="sm" variant="ghost">Details</sb-button>
</sb-card>
Linked card
<sb-card heading="Read the Rocket reference" href="https://data-star.dev/reference/rocket" variant="glow" style="inline-size: 18rem">
Props, codecs, slots and scoping, straight from the source.
</sb-card>
Styling
Style it from your page's CSS, without changing the component or importing anything into it. Custom properties, inherited properties and ::part() all reach into its shadow root.
- Size: it fills the width it is given (a grid cell, say) and tightens its padding when that is under
14rem; in a flex row or any other fit-content layout it takes the width of its content. Setmax-inline-sizeon the card to cap it. Cards in one grid row stretch to the same height, with their footers at the bottom. - Body: normal flow, like a
<div>: inline markup stays in its line, and block children (paragraphs, lists) keep their own margins. - Media: scaled to the card's width and clipped to its corners. Nothing else is clipped, so tooltips and badges can reach outside the card. Raster pixel art that should stay crisp when scaled needs
image-rendering: pixelatedon your<img>. - Fonts: the heading and the body use your page's font.
- Colours: the surface is
--sb-surface-card(--sb-surface-insetforvariant="inset") with a--sb-borderedge; the heading is--sb-text-1and the body--sb-text-2.--sb-brandtints the border of theglowvariant and of a linked card on hover and keyboard focus; keyboard focus also rings the card with--sb-focus-ring. Corners are--sb-radius-lg. - Motion: a linked card lifts by 2px on hover and focus, except with
prefers-reduced-motion. - Parts:
card(the frame),media,heading,bodyandfooter. Your page's::part()rules win over the component's own, without!important.
<style>
.my-card { max-inline-size: 20rem; --sb-surface-card: var(--sb-brand-subtle); --sb-radius-lg: 0; }
.my-card::part(body) { padding: 1.5rem; }
.my-card::part(heading) { font-size: 1.25rem; }
</style>
<sb-card class="my-card" heading="Planet X-9">A cold, quiet world with excellent stargazing.</sb-card>
Accessibility
A linked card renders one real <a> in the heading and stretches its click area over the card, so screen readers hear a single link; without a heading there is no link. Keyboard focus rings the whole card. <a> and <button> elements placed directly in the body, and everything in the footer, stay clickable on their own; give other interactive content in the body of a linked card (an <sb-button>, a link inside a paragraph) position: relative, so it sits above the card's link. Always give media images alt text.
Installation
Add Datastar with Rocket and the Starbase autoloader once per page, then use the tag: the autoloader imports each component the first time its tag appears, including tags a Datastar morph adds later. The Datastar here is v1.0.4 with Starbase's fixes until they are released; the components also run on the official release.
<script type="importmap">
{ "imports": { "datastar": "https://starbase.zweiundeins.gmbh/c/datastar@f602fbe19d74/datastar-rocket.js" } }
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/autoloader.js"></script>
<sb-card heading="Planet X-9">A cold, quiet world with excellent stargazing.</sb-card>No flash of undefined elements: put class="sb-cloak" on <html> and add .sb-cloak :not(:defined) { visibility: hidden } to your CSS. The autoloader removes the class once the page's components are defined.
Load just this component, pinned to this version: all its modules in one minified file with its integrity hash, so the browser refuses it if a single byte changes.
<script type="importmap">
{
"imports": { "datastar": "https://starbase.zweiundeins.gmbh/c/datastar@f602fbe19d74/datastar-rocket.js" },
"integrity": {
"https://starbase.zweiundeins.gmbh/c/datastar@f602fbe19d74/datastar-rocket.js": "sha384-mvTnynHaljKNMleD0bj6/rWROnluHlNmbc5rOQTd+fQH3YBDYhlvHdNquEX99by5"
}
}
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/card@abe5f1868935/card.bundle.min.js" integrity="sha384-yer9K1Wzvw8FO/laJTbrjanP+AczNO7kGc7L1OKCvtYrrh3d9adJ4GeoYUTqT89y"></script>
<sb-card heading="Planet X-9">A cold, quiet world with excellent stargazing.</sb-card>Or the minified module files (one file, which the browser requests as it finds their imports), each pinned in the import map:
<script type="importmap">
{
"imports": { "datastar": "https://starbase.zweiundeins.gmbh/c/datastar@f602fbe19d74/datastar-rocket.js" },
"integrity": {
"https://starbase.zweiundeins.gmbh/c/datastar@f602fbe19d74/datastar-rocket.js": "sha384-mvTnynHaljKNMleD0bj6/rWROnluHlNmbc5rOQTd+fQH3YBDYhlvHdNquEX99by5",
"https://starbase.zweiundeins.gmbh/c/card@abe5f1868935/card.min.js": "sha384-wLH9/At4W/ZFt9c/rQlOjQXaIBhupuvfmY53uq+7JTQIixd7PpPGmUggn4sJYVZ3"
}
}
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/card@abe5f1868935/card.min.js" integrity="sha384-wLH9/At4W/ZFt9c/rQlOjQXaIBhupuvfmY53uq+7JTQIixd7PpPGmUggn4sJYVZ3"></script>
<sb-card heading="Planet X-9">A cold, quiet world with excellent stargazing.</sb-card>In production, pin today's whole catalog: its autoloader and an import map with integrity hashes, so the browser refuses any file that changed. This map covers Datastar and this component; importmap.json has the hashes of every component, to merge into yours.
<script type="importmap">
{
"imports": { "datastar": "https://starbase.zweiundeins.gmbh/c/datastar@f602fbe19d74/datastar-rocket.js" },
"integrity": {
"https://starbase.zweiundeins.gmbh/c/datastar@f602fbe19d74/datastar-rocket.js": "sha384-mvTnynHaljKNMleD0bj6/rWROnluHlNmbc5rOQTd+fQH3YBDYhlvHdNquEX99by5",
"https://starbase.zweiundeins.gmbh/c/card@abe5f1868935/card.bundle.min.js": "sha384-yer9K1Wzvw8FO/laJTbrjanP+AczNO7kGc7L1OKCvtYrrh3d9adJ4GeoYUTqT89y"
}
}
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/@339249aca48f/autoloader.js" integrity="sha384-ZNpMayI1XShztIEX0iDHyCDDbhT8H+t+zEESUq4BCrN4b+7m931LQEl5x0vbY+Vo"></script>
<sb-card heading="Planet X-9">A cold, quiet world with excellent stargazing.</sb-card>Components import only datastar and their own files, so copying is enough: save each component's one file (and a file it loads on first use, keeping its folder), and point the import map at your own copy of datastar-rocket.js (the /js/ paths stand for yours).
<sb-card>card.bundle.min.jsone file 1.2 kB brotli
<script type="importmap">
{ "imports": { "datastar": "/js/datastar-rocket.js" } }
</script>
<script type="module" src="/js/card/card.bundle.min.js"></script>
<sb-card heading="Planet X-9">A cold, quiet world with excellent stargazing.</sb-card>Or copy the module files, keeping each component's folder, and load each component's module:
<script type="importmap">
{ "imports": { "datastar": "/js/datastar-rocket.js" } }
</script>
<script type="module" src="/js/card/card.min.js"></script>
<sb-card heading="Planet X-9">A cold, quiet world with excellent stargazing.</sb-card>Size
A page loads this component as one file, its bundle, compressed the way it is served (gzip -9, brotli -11): brotli is what it downloads. Datastar and Rocket are shared by every component and not counted.
| File | Original | gzip | brotli |
|---|---|---|---|
card.bundle.min.js | 2.9 kB | 1.4 kB | 1.2 kB |
As module files: one request, 1.2 kB minified with brotli
For a page that loads the module files as they are, requesting each one as it finds its import, or bundles them itself. Each file compressed on its own; the minified one is what the page downloads, and the readable source is always there too.
| File | Original | gzip | brotli | minified |
|---|---|---|---|---|
card.js | 4.8 kB | 2.2 kB | 1.8 kB | 1.2 kB |
API reference
Props
| Attribute | Type | Default | Description |
|---|---|---|---|
heading | string | "" | Card title. |
href | string | "" | Makes the whole card a link: the heading is the link, so it needs `heading`. |
variant | "default" | "inset" | "glow" | "default" | Surface style. |
Slots
| Name | Description |
|---|---|
media | Image or illustration on top, edge to edge. |
default | Body content. |
footer | Actions or metadata at the bottom. |