Skip to content

Components

Each component is specified by anatomy, variants, states and rules, so it can be rebuilt in any framework. The working CSS ships as @abhishekshankar/ds/components.css (class names in brackets), and https://ds.abhishekshankar.com shows each one rendered. When porting, keep the values and states and rename the classes freely.

"Derived" means the site doesn't have this component yet. It was designed to match the system.


Button (derived) [ds-button]

  • Anatomy: label, plus an optional leading/trailing 16–20px icon, 8px gap.
  • Size: 40px min height, 16px horizontal padding, radius 10, 14px/500 text.
  • Variants: primary (inverse fill, on-inverse text; at most one per view), secondary (raised fill + border, border strengthens on hover), ghost (secondary text, hover fill 3%).
  • States: hover 150ms standard, focus-visible ring, disabled at 40% opacity.
  • Rules: sentence case, short verbs. No accent-colored buttons, no shadows, no scale on press.

Icon button [ds-icon-button]

  • 44×44 target, 20px glyph, radius 4, secondary color. Hover goes primary with a 3% fill.
  • It must have an aria-label. Toggles (play/pause) update both the icon and the label.

Link and arrow [ds-link, ds-arrow]

  • Idle secondary, hover primary. No underline.
  • Arrow glyphs: → internal, ↗ external. Nudge on hover (see interaction-motion.md).

Eyebrow [ds-eyebrow]

  • 12px, 600, uppercase, +0.09em, secondary color. Sits 18px above a section heading or at the top of a card.
  • An eyebrow can carry an index ("01 · Outcome") or a 28px icon with radius 7.
  • On inverse callouts it takes the accent color.

Pill / caption [ds-pill]

  • 13px, 4px × 10px padding, radius 999, raised fill + border.
  • Used for captions, tags and filter chips. Chips inside dense widgets use radius 6 and 32px height.

Field [ds-field]

  • Anatomy: a bordered container (radius 10, raised fill) holding an input (11 × 14 padding, 14px) and an optional trailing action button separated by a 1px border.
  • States: focus-within means a strong border plus a 3px halo; hover on the trailing button means primary text plus a 3% fill; error means 12.5px danger text below.
  • Rules: the label is visible or aria-label; placeholders are secondary color, never the only label.

Tile [ds-tile, ds-tile__caption]

  • A bento-grid cell: raised fill, 1px border, radius 16, overflow: hidden, content centered.
  • Variants: light (default), inverse (dark, for one or two hero tiles), overflow (content may spill past the edge).
  • Caption: a pill 14px from the bottom, centered, that rises in on hover/focus and is always visible on touch. If the tile body is interactive, the caption becomes the link and gets ↗.
  • Rules: a tile is a link only if it has no inner controls. Media fills the tile with object-fit: cover and the same radius.

Bento grid (pattern)

  • 6 columns on desktop with proportional fr rows and an aspect-ratio on the grid, 2 columns on tablet, 1 on phone.
  • Tiles are placed by grid-area name, with a layout per breakpoint in grid-template-areas.
  • Gap 20 / 16 / 12. Below 1024, rows size to content and each tile sets its own aspect-ratio or min-height.

Card [ds-card]

  • Anatomy (top to bottom, pushed apart with margin-top: auto): meta row (eyebrow and optional index), metric (38–64px, 400, −0.04em), title (H3), body (serif 14px, secondary), and an optional footer (12.5px, top border).
  • Variants: default (raised), inverse, accent (inverse plus a teal radial glow at the bottom right), and compact (min height 190 instead of 230).
  • Rules: one metric per card, short body. Cards in a row share a height.

Callout [ds-callout]

  • Pulls one idea out of the flow. Layout split (label/title in a left column, body on the right; stacks under 700px) or stack.
  • Tones: neutral (default), accent (8% teal tint + 24% teal border), inverse (at most one strong moment per page).
  • The body is serif 15px at 1.62 leading.

Section [ds-section]

  • Container width with --space-section vertical padding and an optional top divider.
  • Header: eyebrow + H2 (editorial: 400, tight) on the left, serif lede on the right, aligned to the bottom. Collapses under 820px.
  • Variants: stack (heading and lede stacked, max 920px), rail (sticky-feeling left column with the heading, body on the right, gap up to 160px), prose (body capped to the reading width).

Rows [ds-rows, ds-row]

  • An index list (projects, history, changelog): a 3-column grid (meta 52px, title, aside right-aligned) with 1px rules top and bottom and a 52px min height.
  • Linked rows end the title with an arrow that nudges on hover. Static rows look the same minus the arrow.
  • Responsive: hide the meta at 768, stack the title and aside at 560.
  • The meta uses tabular numbers.

Facts [ds-facts]

  • Key/value metadata (<dl>): two-column rows with hairline rules, 13.5px, key in secondary and value in primary.
  • Alternative: an eyebrow-style dt over a dd, in a 3-column grid (about-page pattern).

Floating sheet [ds-sheet]

  • A bottom-anchored panel, max 480px, radius 20 (16 on mobile), surface-0, --shadow-float, 20px padding.
  • Header: a 36px icon well (radius 10, raised, bordered) + title (14/500) + subtitle (13, secondary).
  • Enters with the sheet-rise motion. It's the only resting surface with a shadow.

Navbar [ds-navbar]

  • 64px tall, aligned to the center column. Two-tone logo on the left (first name primary, last name secondary, 18/500), text links on the right (16, secondary to primary, 40px gap, current page in primary).
  • Under 768px: links collapse into a 44px morphing hamburger and a full-screen menu (24px links, −0.02em, 24px gap) that locks scroll.
  • The navbar persists across page transitions and doesn't animate.

Footer [ds-footer]

  • Top divider, generous padding (81px top and bottom on desktop, 48px on mobile), link columns aligned right with 14px/500 headings.
  • Bottom bar: a hairline border with copyright on the left and a colophon on the right, 14px secondary. Stacks on mobile.

Hero (pattern)

  • Center column. Display H1, then a subtitle deck in secondary 28–44px below.
  • An optional rotating line: a single line clipped to 1.18em that steps through phrases with translateY, holds about 2s each, and is disabled under reduced motion.
  • An optional facts <dl> on the right (case-study hero: content left, 260–320px facts column right).

Composing new components

When something isn't listed:

  1. Start from the closest component's surface rules (raised fill, 1px border, radius by size).
  2. Build the hierarchy with type size and primary/secondary color before reaching for fills or icons.
  3. Use only tokens. If you need a new value, add a named token to the project's own *.tokens.css file, mention it, and record it in DS_CANDIDATES.md. Never edit the package.
  4. Define every state (hover, focus-visible, active, disabled, loading, empty, error) plus its motion token.
  5. Check it at 375px, under reduced motion, keyboard-only, and in dark mode (data-theme="dark"). Light is the default it ships in.