Skip to content
RU
Scale
Density
Theme
Accent

Data display

Card

An object one could have dragged. There are as many cards as came from the data — unlike a panel, which is a place on the screen.

The component
worldgen-01
terrain/heightmap.ts · 4 files
Markup
<div class="inst-card">
  <div class="inst-card-title">worldgen-01</div>
  <div class="inst-card-sub">terrain/heightmap.ts · 4 files</div>
</div>

A card is a column with a gap between its children. The header does not press against the content, and the heading does not read as part of the first row of data.

Contract

What Required Why
inst-card-title no The heading of an object. A card without one is just a frame
An <a> or a <button> for a clickable one yes The modifier gives the look, the tag gives the role. A div with a handler is unreachable from the keyboard
A real heading (<h3>) in a list of cards no, but usually yes inst-card-title styles but does not declare: navigation by headings does not work without one

Accessibility

Clickability Through an <a> or a <button> only. The modifier gives the look, the tag gives the role
One target rather than two If the whole card is clickable, there must be no second link inside it: the keyboard gets two stops for one object
The heading inst-card-title is visual. For a list of cards a real heading (<h3>) is in order, so that navigation by headings works
Focus A ring with a 1px gap along the outer edge. The radius is inherited from the card
No shadow Depth is carried by the surface. A shadow in the library means "temporary and above" and belongs to the popover and the modal alone

Variants

Rank

A card takes data-rank like any region — lead · default · support — and the contract is in design principles.

The case worth showing is the one a card is usually in: inside something else. A card carrying no rank of its own is an ordinary card however loud the region holding it, and a card nested in a card recedes a second time, because depth decides that and rank does not.

Example
Current run
Ordinary, inside a lead panel
And one step deeper
Support: a rung down, no edge
And one in error, nested
The tone takes the ground; the edge stays ordinary
Markup
<div class="inst-panel" data-rank="lead">
  <div class="inst-panel-header"><span class="inst-panel-title">Current run</span></div>
  <div class="inst-panel-body inst-stack inst-stack--tight">
    <div class="inst-card">
      <div class="inst-card-title">Ordinary, inside a lead panel</div>
      <div class="inst-card"><div class="inst-card-title">And one step deeper</div></div>
    </div>
    <div class="inst-card" data-rank="support">
      <div class="inst-card-title">Support: a rung down, no edge</div>
    </div>
    <div class="inst-card" data-tone="error">
      <div class="inst-card-title">And one in error, nested</div>
      <div class="inst-card-sub">The tone takes the ground; the edge stays ordinary</div>
    </div>
  </div>
</div>

A nested card carrying a tone takes it on its ground instead of the recess film. It still recedes from its container in every theme — a light theme's tone fill sits under the raised surface, a dark theme's is a translucent lift over a dark one, and the recess travels the same way in each — so containment is not lost to meaning.

Interactive

Example
Markup
<a class="inst-card inst-card--interactive" href="#">
  <div class="inst-card-title">Open the run</div>
  <div class="inst-card-sub">The whole card is the tap target</div>
</a>

The modifier adds a response to hover and focus but does not make the element clickable: that has to be an <a> or a <button>. A card that is a div with a click handler is unreachable from the keyboard and has no role.

API

Classes
NameWhat it does
class
inst-cardThe base one
inst-card-titleThe heading of an object
inst-card-subThe caption under the heading
modifier
inst-card--interactiveA response to hover and focus
inst-card--plainNo edge: the boundary is carried by the step of surface alone. For large regions
Tokens
NameValueWhere from
token
--surface-raisedlight-dark(var(--n-0), var(--n-12))base +1: theme
--borderlight-dark(oklch(0 0 0 / 0.12), oklch(1 0 0 / 0.11))one across every cell
--hairline1pxbase +1: screen
--radius-lg12pxbase +4: scale
--pad-cardvar(--space-6)base +14: density · scale

Why the kit works this way — the constitution · Open source under MIT, github.com/keshon/instrument