# Baseline, Tokens, Layouts, Behaviors 1.1.0 documentation
Source: https://github.com/qorsmond/btlb at ce65475
Overview: https://www.bitstoliveby.com/baseline-tokens-layouts-behaviors/
# Documentation
URL: https://www.bitstoliveby.com/baseline-tokens-layouts-behaviors/docs/
Baseline, Tokens, Layouts, Behaviors is a methodology for structuring CSS so a product stays in step with its design system while both change. It comes as four layer packages on npm and a bundle that imports them in cascade order.
Every other page here is generated from the [repository](https://github.com/qorsmond/btlb): the guide is its README, and the reference pages are read from the token JSON and the layer stylesheets. Each page says where it came from. For the ideas with live examples, start at the [overview](/baseline-tokens-layouts-behaviors/).
## Install
```bash
npm install @btlb/css
```
```css
@import '@btlb/css';
```
The bundle declares the layer order once, `@layer tokens, baseline, layouts, behaviors;`, then imports each layer. To take only some layers, install them separately: `@btlb/tokens`, `@btlb/baseline`, `@btlb/layouts` and `@btlb/behaviors`. Baseline, Layouts and Behaviors all read tokens, so `@btlb/tokens`, or your own tokens with the same names, comes first.
## Adopting it
None of this needs a rewrite. The [guide](/baseline-tokens-layouts-behaviors/docs/guide/#adopting-btlb-incrementally) suggests starting with tokens, then Baseline, then layout patterns, then attribute-driven state, and each step is useful on its own.
---
# The guide
URL: https://www.bitstoliveby.com/baseline-tokens-layouts-behaviors/docs/guide/
BTLB is a small methodology for structuring CSS so your product stays in sync with your design system, even while both are changing.
It gives you four layers — **Baseline → Tokens → Layouts → Behaviors** — and a set of rules for writing CSS against them. When the design system shifts, you update things in a few clear places and those changes flow through.
---
## The Shift in Thinking
Most frontend styling is built decision-by-decision:
> *I need a flex container here. What was that hex code for the border? I need a margin-bottom on the title. I'll add a class for when it's active.*
BTLB replaces that with a layered approach:
> **Baseline** – Start with the HTML. A `` and `` already look correct because Baseline styles semantic elements with tokens.
>
> **Tokens** – Apply the "paint." Use semantic tokens like `var(--surface-card)` and `var(--border-subtle)` instead of reaching for hex codes.
>
> **Layouts** – Manage the space. Wrap items in a `.stack` or `.cluster` instead of writing flex/grid rules from scratch.
>
> **Behaviors** – Handle interaction. Bind state to attributes like `data-state="loading"` and `aria-selected="true"` instead of toggling one-off classes.
You stop making small visual decisions everywhere and start assembling meaningful parts.
---
## Quick Start
### Install and Import
```bash
npm install @btlb/css
```
```css
@import '@btlb/css';
```
You can also import per-layer (`@btlb/baseline`, `@btlb/tokens`, `@btlb/layouts`, `@btlb/behaviors`) for more control.
### Step 1: Baseline — Semantic HTML That Already Works
No classes, no attributes. Just HTML and Baseline doing its job.
```html
Product Name
Premium quality with free shipping.
Buy now
```
Baseline styles typography, colors, and spacing using tokens. The button looks like a primary action without you adding anything. If your semantic tokens change, this markup updates automatically.
### Step 2: Tokens — Make the Values Explicit
Your token file separates **primitives** (raw scales) from **semantics** (what values mean in the UI):
```css
:root {
/* Primitives — raw values */
--color-blue-600: #2563eb;
--space-4: 1rem;
/* Semantics — purpose */
--color-bg-interactive: var(--color-blue-600);
--color-text-primary: var(--color-gray-900);
--button-padding-x: var(--space-4);
}
```
Baseline and components consume only semantics:
```css
@layer baseline {
button {
padding-inline: var(--button-padding-x);
background: var(--color-bg-interactive);
color: var(--color-text-on-interactive);
}
}
```
The rule is strict: **components never reference primitives or hardcoded values.** If the brand changes, you update the semantic mapping once.
### Step 3: Layouts — A Pattern, Not a Random Flexbox
Instead of repeating `display: flex; flex-direction: column; gap: …` everywhere, you name the pattern:
```html
Product Name
Premium quality with free shipping.
Buy now
```
The CSS behind it:
```css
@layer layouts {
.stack {
display: flex;
flex-direction: column;
gap: var(--stack-gap, var(--space-4));
}
.stack[data-gap="2"] { --stack-gap: var(--space-2); }
.stack[data-gap="4"] { --stack-gap: var(--space-4); }
.stack[data-gap="6"] { --stack-gap: var(--space-6); }
}
```
Data attributes configure the pattern. The class declares **intent** ("this is a stack"), the attribute declares **configuration** ("with this gap"). Reading the markup tells you *why* elements are grouped, not just *how* they are styled.
### Step 4: Behaviors — State via Attributes
State is expressed through attributes that Behaviors CSS reads:
```html
Product Name
Premium quality with free shipping.
Buy now
```
```css
@layer behaviors {
button[data-state="loading"] {
color: transparent;
pointer-events: none;
}
button[data-state="loading"]::after {
content: '';
inline-size: 1em;
block-size: 1em;
border-radius: 999px;
border: var(--border-width-sm) solid currentColor;
border-inline-end-color: transparent;
animation: spin var(--motion-duration-spin) linear infinite;
}
@keyframes spin { to { rotate: 1turn; } }
}
```
Your JavaScript (any framework) only toggles attributes:
```js
button.dataset.state = 'loading';
button.setAttribute('aria-busy', 'true');
```
No magic class names, no inline styles. The same attribute that CSS reads is the same attribute a screen reader uses.
**That's the full model.** Baseline made the HTML look like UI. Tokens control all values. Layout patterns describe space. Behaviors respond to attributes.
---
## The Four Layers in Detail
### Baseline
Semantic HTML, styled with tokens. Headings, paragraphs, buttons, inputs, and links look like your brand with zero classes. When design tightens typography or adjusts surface colors, you update Baseline once.
### Tokens
The single source of truth for visual decisions. Split into two tiers:
- **Primitives** — raw scales: `--color-blue-600`, `--space-4`, `--font-size-lg`.
- **Semantics** — roles: `--color-bg-surface`, `--card-padding`, `--color-text-primary`.
Components only use semantics. If brand colors, elevation, or density change, you update semantic tokens. You can support multiple themes or brands without rewriting components.
**Fluid tokens** handle values that should differ between a phone and a desktop (type sizes, section spacing). Write the range once with `clamp()` and components never think about screen size. When the value belongs to a box rather than the screen (a card's padding, a heading inside a panel), use container units: `cqi` reads the nearest container, and with none it falls back to the viewport.
```css
:root {
--font-size-h1: clamp(2rem, 1.4rem + 2.6vw, 3.5rem);
--card-padding: clamp(1.25rem, 7cqi, 3rem); /* grows with the card's container */
}
```
**Bridge Tokens** remain for the rarer case where a value must jump rather than scale (e.g., two distinct type ramps). Instead of forcing components to pick between `--font-size-desktop-h1` and `--font-size-mobile-h1`, a bridge token resolves it once with a mobile-first default and a media query override:
```css
:root {
--font-size-h1: var(--font-size-mobile-h1);
}
@media (min-width: 900px) {
:root {
--font-size-h1: var(--font-size-desktop-h1);
}
}
```
Downstream tokens and components reference `--font-size-h1` and never think about breakpoints.
### Layouts
A small set of composable patterns:
| Pattern | Purpose | Use for |
|---|---|---|
| `.stack` | Vertical rhythm between siblings | Forms, cards, sections, dialogs |
| `.cluster` | Horizontal group with wrapping | Button rows, tags, toolbars |
| `.grid` | Responsive cards with min width | Product grids, dashboards |
| `.sidebar` | Fixed side + flexible main | App shells, docs, settings |
| `.center` | Constrain width for readability | Articles, marketing pages |
| `.switcher` | Side by side while every part has room, stacked when not | Hero splits, media + text, two-pane demos |
Others (`.frame`, `.reel`) follow the same convention: **class = intent, data attributes = configuration, tokens = values.**
**Respond to the space you are given.** `.grid`, `.sidebar` and `.switcher` change shape by their own width, with no breakpoint, so a component looks right in a page column, a card or a sidebar. Reach for them first; use a container query when a component rearranges its own insides (give its root `container-type: inline-size`); keep viewport media queries for what is genuinely about the screen, such as sticky chrome or a first-screen height.
```html
```
```html
```
When the design system adjusts spacing or layout rules, you update the pattern once — not every screen.
### Behaviors
How components respond to state and interaction:
- **State**: `data-state="idle | loading | success | error | active | disabled"`
- **Motion**: `data-motion="fade"` with tokenized durations and easings.
- **Accessibility**: `aria-busy`, `aria-pressed`, `aria-expanded` in tandem with `data-state`.
```css
[data-motion="fade"] {
transition: opacity var(--motion-duration-default) var(--motion-easing-standard);
}
@media (prefers-reduced-motion: reduce) {
[data-motion] {
transition-duration: 0ms;
animation-duration: 0ms;
}
}
```
Interaction rules are centralized. When you refine focus rings, loading animations, or error highlighting, you change them in one place. The state contract works with any framework.
Behaviors assume no mouse: hover effects live under `@media (hover: hover)` so a tap never leaves an element stuck mid-hover, and under `@media (pointer: coarse)` Baseline's controls are at least `--spacing-touch-target` (44px) tall.
---
## Modern CSS Under the Hood
BTLB leans on a few modern CSS features in service of the four-layer model:
- **Cascade layers (`@layer`)** keep override order predictable. Baseline, Tokens, Layouts, and Behaviors each live in their own layer so you know what wins when things conflict.
- **Custom properties (`var()`)** are how tokens live in code. You can change values at theme, page, or component scope without changing selectors.
- **Container queries and logical properties** help layouts adapt to the space they're in, not just the viewport.
---
## Do's and Don'ts
### Do
- Start with semantic HTML and let Baseline do as much as possible.
- Use semantic tokens in components — never raw hex, never primitives.
- Reach for layout patterns before writing custom flex/grid.
- Express state with `data-state`, `data-behavior`, and `aria-*` attributes.
- Treat components as assemblies of the four layers, not a separate styling world.
### Don't
- Hardcode visual values (colors, spacing, radii, shadows, motion) in components.
- Reference primitives like `--color-blue-600` directly from component CSS.
- Invent one-off flex/grid layouts when a pattern would work.
- Hide state in class names that nobody can decode (`.is-active-v2`, `.card--special`).
---
## Components Are Consumers, Not a Fifth Layer
A component is just a predictable assembly of the four layers. A card, for example, is:
- **Baseline** typography and button defaults.
- **Semantic tokens** for its surface, border, padding, and gap.
- **Layout patterns** like `.stack` and `.cluster` for internal structure.
- **Behaviors** for hover, focus, active, loading, and error states.
If card padding changes in the design system, you remap `--card-padding` in tokens. If active cards should elevate more, you update the behavior rule. If stacks get new spacing, you update the pattern. The component CSS itself rarely changes.
---
## Why Strict Layers Help with AI Code Generation
If you use AI tools to generate frontend code, BTLB gives the model a constrained vocabulary: named tokens instead of arbitrary values, named patterns instead of ad-hoc flex/grid, and attributes instead of invented class names. The output becomes predictable and consistent with your system rather than a grab-bag of styles.
---
## Adopting BTLB Incrementally
BTLB is not a rewrite. You can adopt it layer by layer:
1. **Start with Tokens.** Separate your existing CSS variables into primitives and semantics. Stop hardcoding values in components.
2. **Add Baseline.** Style semantic HTML elements with your tokens so bare markup looks right.
3. **Introduce Layout patterns.** When you see the same flex/grid recipe repeated, extract it into a named pattern.
4. **Move to attribute-driven Behaviors.** Replace state class names with `data-state` and `aria-*` attributes as you touch components.
Each step improves things on its own. You don't need all four layers to start getting value.
---
## Origin
BTLB is curated and maintained by **Quintin Orsmond**. It grew out of real product work where design systems and products had to evolve together without drifting apart — fewer magic values, more explicit contracts, cleaner markup, and modern CSS features organized into a mental model small enough to hold in your head.
---
# Behavior reference
URL: https://www.bitstoliveby.com/baseline-tokens-layouts-behaviors/docs/reference/behaviors/
Behaviors read attributes. Your JavaScript, in any framework, sets them; the stylesheet and assistive technology read the same markup.
## States
- `data-state="loading"`
- `data-state="success"`
- `data-state="error"`
- `data-state="warning"`
- `data-state="info"`
- `data-state="active"`
- `data-state="disabled"`
- `data-state="idle"`
- `data-state="hidden"`
## Motion
- `data-motion="fade"`
- `data-motion="slide-up"`
- `data-motion="scale"`
- `data-motion="interactive"`
Every `[data-motion]` element drops its transitions and animations under `prefers-reduced-motion: reduce`.
## ARIA attributes with styles
- `[aria-disabled="true"]`
- `[aria-live]`
## Source
### states.css
```css
@layer behaviors {
/* Loading state */
[data-state="loading"] {
position: relative;
color: transparent;
pointer-events: none;
}
[data-state="loading"]::after {
content: '';
position: absolute;
inset: 0;
margin: auto;
inline-size: 1.25em;
block-size: 1.25em;
border-radius: 50%;
border: 2px solid currentColor;
border-inline-end-color: transparent;
animation: spin var(--motion-duration-slower) linear infinite;
color: var(--color-text-on-interactive);
}
@keyframes spin {
to {
rotate: 1turn;
}
}
/* Success state */
[data-state="success"] {
background: var(--color-status-success-bg);
color: var(--color-status-success-text);
border-color: var(--color-status-success-border);
}
/* Error state */
[data-state="error"] {
background: var(--color-status-error-bg);
color: var(--color-status-error-text);
border-color: var(--color-status-error-border);
}
/* Warning state */
[data-state="warning"] {
background: var(--color-status-warning-bg);
color: var(--color-status-warning-text);
border-color: var(--color-status-warning-border);
}
/* Info state */
[data-state="info"] {
background: var(--color-status-info-bg);
color: var(--color-status-info-text);
border-color: var(--color-status-info-border);
}
/* Active state */
[data-state="active"] {
background: var(--color-bg-interactive);
color: var(--color-text-on-interactive);
border-color: var(--color-border-interactive);
}
/* Disabled state */
[data-state="disabled"],
[disabled] {
opacity: 0.5;
pointer-events: none;
cursor: not-allowed;
}
/* Interactive states for buttons specifically */
button[data-state="idle"]:hover {
background: var(--color-bg-interactive-hover);
}
button[data-state="idle"]:active {
background: var(--color-bg-interactive-active);
transform: scale(0.98);
}
}
```
### motion.css
```css
@layer behaviors {
/* Fade motion */
[data-motion="fade"] {
transition: opacity var(--motion-duration-base) var(--motion-easing-out);
}
[data-motion="fade"][data-state="hidden"],
[data-motion="fade"][hidden] {
opacity: 0;
}
/* Slide up motion */
[data-motion="slide-up"] {
transition:
transform var(--motion-duration-base) var(--motion-easing-out),
opacity var(--motion-duration-base) var(--motion-easing-out);
}
[data-motion="slide-up"][data-state="hidden"],
[data-motion="slide-up"][hidden] {
transform: translateY(1rem);
opacity: 0;
}
/* Scale motion */
[data-motion="scale"] {
transition:
transform var(--motion-duration-base) var(--motion-easing-spring),
opacity var(--motion-duration-base) var(--motion-easing-out);
}
[data-motion="scale"][data-state="hidden"],
[data-motion="scale"][hidden] {
transform: scale(0.95);
opacity: 0;
}
/* Interactive motion hints */
[data-motion="interactive"] {
transition: transform var(--motion-duration-fast) var(--motion-easing-out);
}
/* Only where the pointer can hover: on a touch screen a tap would leave
the element lifted. */
@media (hover: hover) {
[data-motion="interactive"]:hover {
transform: translateY(-2px);
}
}
[data-motion="interactive"]:active {
transform: translateY(0);
}
/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
[data-motion],
[data-motion] * {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Duration utilities */
[data-motion-duration="fast"] {
--motion-duration: var(--motion-duration-fast);
}
[data-motion-duration="base"] {
--motion-duration: var(--motion-duration-base);
}
[data-motion-duration="slow"] {
--motion-duration: var(--motion-duration-slow);
}
/* Easing utilities */
[data-motion-easing="linear"] {
--motion-easing: var(--motion-easing-linear);
}
[data-motion-easing="in"] {
--motion-easing: var(--motion-easing-in);
}
[data-motion-easing="out"] {
--motion-easing: var(--motion-easing-out);
}
[data-motion-easing="in-out"] {
--motion-easing: var(--motion-easing-inOut);
}
[data-motion-easing="spring"] {
--motion-easing: var(--motion-easing-spring);
}
}
```
### accessibility.css
```css
@layer behaviors {
/* Screen reader only */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
/* Focus visible styles */
:focus-visible {
outline: 2px solid var(--color-border-focus);
outline-offset: 2px;
border-radius: var(--radius-sm);
}
/* Skip link */
.skip-link {
position: absolute;
top: var(--space-2);
left: var(--space-2);
padding: var(--space-2) var(--space-4);
background: var(--color-bg-interactive);
color: var(--color-text-on-interactive);
border-radius: var(--radius-md);
text-decoration: none;
transform: translateY(-200%);
transition: transform var(--motion-duration-fast) var(--motion-easing-out);
z-index: 9999;
}
.skip-link:focus {
transform: translateY(0);
}
/* Focus within - show focus indicator for parent when child is focused */
[data-focus-within]:focus-within {
outline: 2px solid var(--color-border-focus);
outline-offset: 2px;
}
/* Keyboard navigation indicator */
:focus:not(:focus-visible) {
outline: none;
}
/* Disabled interactive elements */
[aria-disabled="true"] {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
/* Live regions for announcements */
[aria-live] {
position: absolute;
left: -10000px;
width: 1px;
height: 1px;
overflow: hidden;
}
[aria-live][data-visible] {
position: static;
width: auto;
height: auto;
}
/* High contrast mode support */
@media (prefers-contrast: high) {
:focus-visible {
outline-width: 3px;
outline-offset: 3px;
}
button,
input,
select,
textarea {
border-width: 2px;
}
}
/* Respect prefers-reduced-transparency */
@media (prefers-reduced-transparency: reduce) {
* {
backdrop-filter: none !important;
}
}
}
```
---
# Layout reference
URL: https://www.bitstoliveby.com/baseline-tokens-layouts-behaviors/docs/reference/layouts/
Each pattern is a class that declares intent. Its data attributes choose a configuration, and the values come from tokens. An attribute value that is not listed here does nothing.
## .stack
Stack - Vertical rhythm.
```css
.stack { display: flex; flex-direction: column; gap: var(--stack-gap, var(--space-4)); }
```
| Attribute | Values | Sets |
| --- | --- | --- |
| `data-gap` | `0` `1` `2` `3` `4` `5` `6` `7` `8` `10` `12` `tight` `normal` `loose` | `--stack-gap` |
| `data-align` | `start` `center` `end` `stretch` | `align-items` |
| `data-justify` | `start` `center` `end` `between` | `justify-content` |
## .cluster
Cluster - Horizontal grouping with wrapping.
```css
.cluster { display: flex; flex-wrap: wrap; gap: var(--cluster-gap, var(--space-3)); align-items: var(--cluster-align, center); }
```
| Attribute | Values | Sets |
| --- | --- | --- |
| `data-gap` | `0` `1` `2` `3` `4` `5` `6` `8` `tight` `normal` `loose` | `--cluster-gap` |
| `data-align` | `start` `center` `end` `baseline` `stretch` | `--cluster-align` |
| `data-justify` | `start` `center` `end` `between` `around` | `justify-content` |
## .grid
Grid - Responsive cards with minimum width.
```css
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(var(--grid-min, 280px), 100%), 1fr)); gap: var(--grid-gap, var(--space-5)); }
```
| Attribute | Values | Sets |
| --- | --- | --- |
| `data-min` | `200` `240` `280` `320` `360` `400` | `--grid-min` |
| `data-gap` | `2` `3` `4` `5` `6` `8` `10` | `--grid-gap` |
| `data-align` | `start` `center` `end` `stretch` | `align-items` |
## .sidebar
Sidebar - Fixed sidebar + flexible main.
```css
.sidebar { display: flex; flex-wrap: wrap; gap: var(--sidebar-gap, var(--space-6)); }
.sidebar > :first-child { flex-basis: var(--sidebar-width, 16rem); flex-grow: 0; flex-shrink: 0; }
.sidebar > :last-child { flex-basis: 0; flex-grow: 999; min-inline-size: 50%; }
```
| Attribute | Values | Sets |
| --- | --- | --- |
| `data-side` | `end` | `> ` |
| `data-sidebar-width` | `12` `14` `16` `18` `20` `24` | `--sidebar-width` |
| `data-gap` | `4` `5` `6` `8` `10` | `--sidebar-gap` |
## .center
Center - Constrain width, keep things readable.
```css
.center { box-sizing: content-box; margin-inline: auto; max-inline-size: var(--center-max, 70ch); padding-inline: var(--center-padding, var(--space-6)); }
```
| Attribute | Values | Sets |
| --- | --- | --- |
| `data-max` | `50` `60` `70` `80` `90` `40rem` `48rem` `56rem` `64rem` `80rem` | `--center-max` |
| `data-padding` | `4` `6` `8` `0` | `--center-padding` |
| `data-text` | `center` | `text-align` |
## .switcher
Switcher - side by side while every part has room, stacked when not. The threshold is the pattern's own inline size at which the parts stop fitting; no media or container query is involved, so it responds to whatever space it is given, not to the viewport..
```css
.switcher { display: flex; flex-wrap: wrap; gap: var(--switcher-gap, var(--space-6)); align-items: var(--switcher-align, stretch); }
.switcher > * { flex-grow: 1; flex-basis: calc((var(--switcher-threshold, 40rem) - 100%) * 999); min-inline-size: 0; }
```
| Attribute | Values | Sets |
| --- | --- | --- |
| `data-threshold` | `30` `36` `40` `44` `48` `52` `56` | `--switcher-threshold` |
| `data-ratio` | `4:3` `3:2` `2:1` `3:4` `2:3` `1:2` | `> ` |
| `data-gap` | `4` `6` `8` `12` `loose` | `--switcher-gap` |
| `data-align` | `start` `center` `end` | `--switcher-align` |
---
# Token reference
URL: https://www.bitstoliveby.com/baseline-tokens-layouts-behaviors/docs/reference/tokens/
66 semantic tokens point at 132 primitives. Components and the Baseline layer are meant to use the semantic tier; the primitives are the raw scales it maps from.
## Semantic tokens
### colors
From `packages/tokens/tokens/semantic/colors.json`.
| Token | Points to | Resolves to | Purpose |
| --- | --- | --- | --- |
| `--color-bg-surface` | `--color-white` | `#ffffff` | Default background for surfaces like cards, panels |
| `--color-bg-raised` | `--color-gray-50` | `#f9fafb` | Elevated surfaces, modals, popovers |
| `--color-bg-sunken` | `--color-gray-100` | `#f3f4f6` | Recessed areas, wells |
| `--color-bg-canvas` | `--color-gray-50` | `#f9fafb` | Page/app background |
| `--color-bg-interactive` | `--color-blue-600` | `#2563eb` | Primary interactive elements (buttons, links) |
| `--color-bg-interactive-hover` | `--color-blue-700` | `#1d4ed8` | Hover state for interactive elements |
| `--color-bg-interactive-active` | `--color-blue-800` | `#1e40af` | Active/pressed state for interactive elements |
| `--color-bg-subtle` | `--color-gray-100` | `#f3f4f6` | Subtle background for secondary actions |
| `--color-bg-subtle-hover` | `--color-gray-200` | `#e5e7eb` | Hover state for subtle backgrounds |
| `--color-text-primary` | `--color-gray-900` | `#111827` | Primary text color |
| `--color-text-secondary` | `--color-gray-600` | `#4b5563` | Secondary, less emphasized text |
| `--color-text-tertiary` | `--color-gray-500` | `#6b7280` | Tertiary, de-emphasized text |
| `--color-text-on-interactive` | `--color-white` | `#ffffff` | Text on interactive backgrounds |
| `--color-text-link` | `--color-blue-600` | `#2563eb` | Link text color |
| `--color-text-link-hover` | `--color-blue-700` | `#1d4ed8` | Link hover color |
| `--color-text-disabled` | `--color-gray-400` | `#9ca3af` | Disabled text |
| `--color-border-default` | `--color-gray-200` | `#e5e7eb` | Default border color |
| `--color-border-strong` | `--color-gray-300` | `#d1d5db` | Stronger borders for emphasis |
| `--color-border-subtle` | `--color-gray-100` | `#f3f4f6` | Subtle borders |
| `--color-border-interactive` | `--color-blue-600` | `#2563eb` | Interactive element borders |
| `--color-border-focus` | `--color-blue-500` | `#3b82f6` | Focus ring color |
| `--color-border-error` | `--color-red-500` | `#ef4444` | Error state borders |
| `--color-border-success` | `--color-green-500` | `#22c55e` | Success state borders |
| `--color-status-success-bg` | `--color-green-50` | `#f0fdf4` | |
| `--color-status-success-text` | `--color-green-700` | `#15803d` | |
| `--color-status-success-border` | `--color-green-200` | `#bbf7d0` | |
| `--color-status-error-bg` | `--color-red-50` | `#fef2f2` | |
| `--color-status-error-text` | `--color-red-700` | `#b91c1c` | |
| `--color-status-error-border` | `--color-red-200` | `#fecaca` | |
| `--color-status-warning-bg` | `--color-amber-50` | `#fffbeb` | |
| `--color-status-warning-text` | `--color-amber-700` | `#b45309` | |
| `--color-status-warning-border` | `--color-amber-200` | `#fde68a` | |
| `--color-status-info-bg` | `--color-blue-50` | `#eff6ff` | |
| `--color-status-info-text` | `--color-blue-700` | `#1d4ed8` | |
| `--color-status-info-border` | `--color-blue-200` | `#bfdbfe` | |
### spacing
From `packages/tokens/tokens/semantic/spacing.json`.
| Token | Points to | Resolves to | Purpose |
| --- | --- | --- | --- |
| `--spacing-touch-target` | `--space-11` | `2.75rem` | Smallest hit area for anything a finger has to press (WCAG 2.5.5) |
| `--spacing-card-padding` | `--space-6` | `1.5rem` | Default padding for cards |
| `--spacing-card-gap` | `--space-4` | `1rem` | Gap between card elements |
| `--spacing-button-padding-x` | `--space-4` | `1rem` | Horizontal padding for buttons |
| `--spacing-button-padding-y` | `--space-2` | `0.5rem` | Vertical padding for buttons |
| `--spacing-button-gap` | `--space-2` | `0.5rem` | Gap between button icon and text |
| `--spacing-input-padding-x` | `--space-3` | `0.75rem` | Horizontal padding for inputs |
| `--spacing-input-padding-y` | `--space-2` | `0.5rem` | Vertical padding for inputs |
| `--spacing-section-padding` | `--space-8` | `2rem` | Section padding |
| `--spacing-section-gap` | `--space-6` | `1.5rem` | Gap between sections |
| `--spacing-page-padding-x` | `--space-6` | `1.5rem` | Horizontal page gutters |
| `--spacing-page-padding-y` | `--space-8` | `2rem` | Vertical page padding |
| `--spacing-stack-gap-tight` | `--space-2` | `0.5rem` | |
| `--spacing-stack-gap-normal` | `--space-4` | `1rem` | |
| `--spacing-stack-gap-loose` | `--space-6` | `1.5rem` | |
| `--spacing-cluster-gap-tight` | `--space-2` | `0.5rem` | |
| `--spacing-cluster-gap-normal` | `--space-3` | `0.75rem` | |
| `--spacing-cluster-gap-loose` | `--space-4` | `1rem` | |
### typography
From `packages/tokens/tokens/semantic/typography.json`.
| Token | Points to | Resolves to | Purpose |
| --- | --- | --- | --- |
| `--typography-body-font-family` | `--font-family-sans` | `system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif` | |
| `--typography-body-font-size` | `--font-size-base` | `1rem` | |
| `--typography-body-font-weight` | `--font-weight-normal` | `400` | |
| `--typography-body-line-height` | `--font-lineHeight-normal` | `1.5` | |
| `--typography-heading-font-family` | `--font-family-sans` | `system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif` | |
| `--typography-heading-font-weight` | `--font-weight-bold` | `700` | |
| `--typography-heading-line-height` | `--font-lineHeight-tight` | `1.25` | |
| `--typography-label-font-size` | `--font-size-sm` | `0.875rem` | |
| `--typography-label-font-weight` | `--font-weight-medium` | `500` | |
| `--typography-caption-font-size` | `--font-size-xs` | `0.75rem` | |
| `--typography-caption-line-height` | `--font-lineHeight-snug` | `1.375` | |
| `--typography-code-font-family` | `--font-family-mono` | `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace` | |
| `--typography-code-font-size` | `--font-size-sm` | `0.875rem` | |
## Primitives
### colors
From `packages/tokens/tokens/primitives/colors.json`.
| Token | Value | Type |
| --- | --- | --- |
| `--color-gray-50` | `#f9fafb` | color |
| `--color-gray-100` | `#f3f4f6` | color |
| `--color-gray-200` | `#e5e7eb` | color |
| `--color-gray-300` | `#d1d5db` | color |
| `--color-gray-400` | `#9ca3af` | color |
| `--color-gray-500` | `#6b7280` | color |
| `--color-gray-600` | `#4b5563` | color |
| `--color-gray-700` | `#374151` | color |
| `--color-gray-800` | `#1f2937` | color |
| `--color-gray-900` | `#111827` | color |
| `--color-gray-950` | `#030712` | color |
| `--color-blue-50` | `#eff6ff` | color |
| `--color-blue-100` | `#dbeafe` | color |
| `--color-blue-200` | `#bfdbfe` | color |
| `--color-blue-300` | `#93c5fd` | color |
| `--color-blue-400` | `#60a5fa` | color |
| `--color-blue-500` | `#3b82f6` | color |
| `--color-blue-600` | `#2563eb` | color |
| `--color-blue-700` | `#1d4ed8` | color |
| `--color-blue-800` | `#1e40af` | color |
| `--color-blue-900` | `#1e3a8a` | color |
| `--color-blue-950` | `#172554` | color |
| `--color-green-50` | `#f0fdf4` | color |
| `--color-green-100` | `#dcfce7` | color |
| `--color-green-200` | `#bbf7d0` | color |
| `--color-green-300` | `#86efac` | color |
| `--color-green-400` | `#4ade80` | color |
| `--color-green-500` | `#22c55e` | color |
| `--color-green-600` | `#16a34a` | color |
| `--color-green-700` | `#15803d` | color |
| `--color-green-800` | `#166534` | color |
| `--color-green-900` | `#14532d` | color |
| `--color-green-950` | `#052e16` | color |
| `--color-red-50` | `#fef2f2` | color |
| `--color-red-100` | `#fee2e2` | color |
| `--color-red-200` | `#fecaca` | color |
| `--color-red-300` | `#fca5a5` | color |
| `--color-red-400` | `#f87171` | color |
| `--color-red-500` | `#ef4444` | color |
| `--color-red-600` | `#dc2626` | color |
| `--color-red-700` | `#b91c1c` | color |
| `--color-red-800` | `#991b1b` | color |
| `--color-red-900` | `#7f1d1d` | color |
| `--color-red-950` | `#450a0a` | color |
| `--color-amber-50` | `#fffbeb` | color |
| `--color-amber-100` | `#fef3c7` | color |
| `--color-amber-200` | `#fde68a` | color |
| `--color-amber-300` | `#fcd34d` | color |
| `--color-amber-400` | `#fbbf24` | color |
| `--color-amber-500` | `#f59e0b` | color |
| `--color-amber-600` | `#d97706` | color |
| `--color-amber-700` | `#b45309` | color |
| `--color-amber-800` | `#92400e` | color |
| `--color-amber-900` | `#78350f` | color |
| `--color-amber-950` | `#451a03` | color |
| `--color-white` | `#ffffff` | color |
| `--color-black` | `#000000` | color |
### motion
From `packages/tokens/tokens/primitives/motion.json`.
| Token | Value | Type |
| --- | --- | --- |
| `--motion-duration-instant` | `0ms` | duration |
| `--motion-duration-fast` | `150ms` | duration |
| `--motion-duration-base` | `250ms` | duration |
| `--motion-duration-slow` | `350ms` | duration |
| `--motion-duration-slower` | `500ms` | duration |
| `--motion-easing-linear` | `cubic-bezier(0, 0, 1, 1)` | cubicBezier |
| `--motion-easing-in` | `cubic-bezier(0.4, 0, 1, 1)` | cubicBezier |
| `--motion-easing-out` | `cubic-bezier(0, 0, 0.2, 1)` | cubicBezier |
| `--motion-easing-inOut` | `cubic-bezier(0.4, 0, 0.2, 1)` | cubicBezier |
| `--motion-easing-spring` | `cubic-bezier(0.34, 1.56, 0.64, 1)` | cubicBezier |
### radii
From `packages/tokens/tokens/primitives/radii.json`.
| Token | Value | Type |
| --- | --- | --- |
| `--radius-none` | `0` | dimension |
| `--radius-sm` | `0.125rem` | dimension |
| `--radius-base` | `0.25rem` | dimension |
| `--radius-md` | `0.375rem` | dimension |
| `--radius-lg` | `0.5rem` | dimension |
| `--radius-xl` | `0.75rem` | dimension |
| `--radius-2xl` | `1rem` | dimension |
| `--radius-3xl` | `1.5rem` | dimension |
| `--radius-full` | `9999px` | dimension |
### shadows
From `packages/tokens/tokens/primitives/shadows.json`.
| Token | Value | Type |
| --- | --- | --- |
| `--shadow-sm` | `0 1px 2px 0 rgba(0, 0, 0, 0.05)` | shadow |
| `--shadow-base` | `0 1px 3px 0 rgba(0, 0, 0, 0.1)` | shadow |
| `--shadow-md` | `0 4px 6px -1px rgba(0, 0, 0, 0.1)` | shadow |
| `--shadow-lg` | `0 10px 15px -3px rgba(0, 0, 0, 0.1)` | shadow |
| `--shadow-xl` | `0 20px 25px -5px rgba(0, 0, 0, 0.1)` | shadow |
| `--shadow-none` | `0 0 0 0 transparent` | shadow |
### spacing
From `packages/tokens/tokens/primitives/spacing.json`.
| Token | Value | Type |
| --- | --- | --- |
| `--space-0` | `0` | dimension |
| `--space-1` | `0.25rem` | dimension |
| `--space-2` | `0.5rem` | dimension |
| `--space-3` | `0.75rem` | dimension |
| `--space-4` | `1rem` | dimension |
| `--space-5` | `1.25rem` | dimension |
| `--space-6` | `1.5rem` | dimension |
| `--space-7` | `1.75rem` | dimension |
| `--space-8` | `2rem` | dimension |
| `--space-10` | `2.5rem` | dimension |
| `--space-11` | `2.75rem` | dimension |
| `--space-12` | `3rem` | dimension |
| `--space-14` | `3.5rem` | dimension |
| `--space-16` | `4rem` | dimension |
| `--space-20` | `5rem` | dimension |
| `--space-24` | `6rem` | dimension |
| `--space-32` | `8rem` | dimension |
| `--space-40` | `10rem` | dimension |
| `--space-48` | `12rem` | dimension |
| `--space-56` | `14rem` | dimension |
| `--space-64` | `16rem` | dimension |
### typography
From `packages/tokens/tokens/primitives/typography.json`.
| Token | Value | Type |
| --- | --- | --- |
| `--font-family-sans` | `system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif` | fontFamily |
| `--font-family-serif` | `Georgia, Cambria, "Times New Roman", Times, serif` | fontFamily |
| `--font-family-mono` | `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace` | fontFamily |
| `--font-size-xs` | `0.75rem` | dimension |
| `--font-size-sm` | `0.875rem` | dimension |
| `--font-size-base` | `1rem` | dimension |
| `--font-size-lg` | `1.125rem` | dimension |
| `--font-size-xl` | `1.25rem` | dimension |
| `--font-size-2xl` | `1.5rem` | dimension |
| `--font-size-3xl` | `1.875rem` | dimension |
| `--font-size-4xl` | `2.25rem` | dimension |
| `--font-size-5xl` | `3rem` | dimension |
| `--font-size-6xl` | `3.75rem` | dimension |
| `--font-size-7xl` | `4.5rem` | dimension |
| `--font-weight-thin` | `100` | fontWeight |
| `--font-weight-extralight` | `200` | fontWeight |
| `--font-weight-light` | `300` | fontWeight |
| `--font-weight-normal` | `400` | fontWeight |
| `--font-weight-medium` | `500` | fontWeight |
| `--font-weight-semibold` | `600` | fontWeight |
| `--font-weight-bold` | `700` | fontWeight |
| `--font-weight-extrabold` | `800` | fontWeight |
| `--font-weight-black` | `900` | fontWeight |
| `--font-lineHeight-none` | `1` | number |
| `--font-lineHeight-tight` | `1.25` | number |
| `--font-lineHeight-snug` | `1.375` | number |
| `--font-lineHeight-normal` | `1.5` | number |
| `--font-lineHeight-relaxed` | `1.625` | number |
| `--font-lineHeight-loose` | `2` | number |
---
# The tokens package
URL: https://www.bitstoliveby.com/baseline-tokens-layouts-behaviors/docs/tokens/
W3C Design Tokens (DTCG spec) for the BTLB design system.
## Installation
```bash
npm install @btlb/tokens
```
## Usage
### Import All Tokens
```css
@import '@btlb/tokens';
```
This imports both primitives and semantic tokens in the correct cascade layer order.
### Import Separately
```css
/* Primitives only */
@import '@btlb/tokens/primitives.css';
/* Semantic tokens only (requires primitives) */
@import '@btlb/tokens/semantic.css';
```
## Token Structure
### Primitives
Raw design values organized by category:
- **Colors**: Gray, blue, green, red, amber scales (50-950)
- **Spacing**: 0-64 scale in rem units
- **Typography**: Font families, sizes, weights, line heights
- **Radii**: Border radius values
- **Shadows**: Box shadow tokens
- **Motion**: Durations and easing curves
Example:
```css
var(--color-blue-600)
var(--space-4)
var(--font-size-lg)
var(--radius-md)
var(--shadow-lg)
var(--motion-duration-base)
```
### Semantics
Purpose-driven tokens that reference primitives:
- **Backgrounds**: `--color-bg-surface`, `--color-bg-interactive`, etc.
- **Text**: `--color-text-primary`, `--color-text-secondary`, etc.
- **Borders**: `--color-border-default`, `--color-border-focus`, etc.
- **Status**: Success, error, warning, info variants
- **Spacing**: `--spacing-card-padding`, `--spacing-button-padding-x`, etc.
- **Typography**: `--typography-body-font-size`, `--typography-heading-font-weight`, etc.
Example:
```css
var(--color-bg-surface)
var(--color-text-primary)
var(--spacing-card-padding)
var(--typography-body-font-size)
```
## Customizing Tokens
### Option 1: Override in Your CSS
```css
@import '@btlb/tokens';
/* Override semantic tokens for your theme */
@layer semantic {
:root {
--color-bg-interactive: var(--color-green-600);
--color-text-primary: var(--color-gray-950);
}
}
```
### Option 2: Import Your Own W3C Tokens
Export design tokens from Penpot, Figma, or create your own following the [W3C DTCG spec](https://tr.designtokens.org/format/):
```css
/* Don't import @btlb/tokens */
/* Import your custom tokens instead */
@import './my-penpot-tokens.css' layer(tokens);
/* Then use BTLB layers that consume those tokens */
@import '@btlb/baseline';
@import '@btlb/layouts';
```
## Source JSON Files
The source W3C Design Token JSON files are included in `tokens/`:
```
tokens/
├── primitives/
│ ├── colors.json
│ ├── spacing.json
│ ├── typography.json
│ ├── radii.json
│ ├── shadows.json
│ └── motion.json
└── semantic/
├── colors.json
├── spacing.json
└── typography.json
```
You can import these in your own Style Dictionary build if you want to extend or modify them.
## W3C DTCG Format
All tokens follow the W3C Design Tokens Community Group format:
```json
{
"color": {
"$type": "color",
"blue": {
"500": {
"$value": "#3b82f6",
"$description": "Primary blue"
}
}
}
}
```
Token references use curly brace syntax:
```json
{
"color": {
"bg": {
"interactive": {
"$value": "{color.blue.600}"
}
}
}
}
```
## License
MIT