Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,36 @@ Previously Chota forced `1rem = 10px`, which overrode the consumer's root font-s
### Note on em-based typography

Em-based typography (`h1`–`h6`, `.tag.is-small`/`.is-large`, `.nav .brand`, `code`/`kbd`) is unchanged — it was always relative to the body font-size, which remains 16px.

## Design Tokens

### What changed

Chota now exposes three component-level design tokens as CSS custom properties, replacing previously hardcoded values:

| Property | Before | After | Default |
|---|---|---|---|
| `--color-placeholder` | `#bdbfc4` (hardcoded in `::placeholder`) | `var(--color-placeholder)` | `#bdbfc4` |
| `--border-radius` | `4px` (hardcoded on `.card`, inputs, buttons, `code`/`kbd`, `.grouped.gapless` outer corners) | `var(--border-radius)` | `4px` |
| `--transition-duration` | `0.2s` (hardcoded on input/button transitions) | `var(--transition-duration)` | `0.2s` |

### Why

Consumers previously had to override these values with specificity battles against Chota's selectors. Exposing them as custom properties lets consumers override once on `:root` (or any selector) and have the change propagate to every affected component.

### Consumer impact

- **Consumers who do not override:** no visual change. Every token resolves to its previous hardcoded value.
- **Consumers who override:** set the token on `:root` after importing `chota.css`:

```css
@import url(chota.css);

:root {
--border-radius: 8px; /* rounder corners on cards, inputs, buttons */
}
```

### Note on `.grouped.gapless`

The `.grouped.gapless` first/last child rules use `var(--border-radius)` for their outer corners (preserving `!important`); the inner corners stay `0` because they are joined-border geometry, not a theming knob. Overriding `--border-radius` to `0` squares both standalone buttons and gapless group outer corners consistently.
14 changes: 7 additions & 7 deletions baselines/size-baseline.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"capturedAt": "2026-08-09T15:15:46.109Z",
"branch": "v1",
"capturedAt": "2026-08-11T00:00:00.000Z",
"branch": "work/138-design-tokens",
"buildCommand": "yarn build",
"browserslist": [
"last 2 Chrome versions",
Expand All @@ -10,10 +10,10 @@
"ios >= 15.4"
],
"source": "src/chota.css",
"dist_chota_css_bytes": 19039,
"dist_chota_min_css_bytes": 14419,
"gzip_bytes": 3373,
"dist_chota_css_bytes": 19323,
"dist_chota_min_css_bytes": 14693,
"gzip_bytes": 3409,
"gzip_ceiling_bytes": 4096,
"gzip_budget_remaining_bytes": 723,
"rationale": "card #137 root-font migration: removed html { font-size: 62.5% } and divided all rem values by 1.6 to preserve rendered pixels at a 16px consumer root. 1-byte gzip increase from marginally longer decimal tokens (e.g. 0.625rem vs 1rem). Body font-size remains 16px (--font-size: 1rem at 16px root). Em-based typography (h1-h6, .tag.is-small/large, .nav .brand, code/kbd) is unchanged."
"gzip_budget_remaining_bytes": 687,
"rationale": "card #138 design tokens: added --color-placeholder, --border-radius, --transition-duration custom properties to :root and replaced 9 hardcoded sites (6 border-radius, 2 transition-duration, 1 placeholder color) with var() references. Defaults preserve rendered output. 36-byte gzip increase from the 3 new :root declarations plus longer var() token references at each substituted site."
}
21 changes: 12 additions & 9 deletions dist/chota.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
"Avenir Next", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
"Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
--font-family-mono: monaco, "Consolas", "Lucida Console", monospace;
--color-placeholder: #bdbfc4;
--border-radius: 4px;
--transition-duration: 0.2s;
}
/* Chota does not set the root font-size. All rem values are relative to the
consumer's html font-size (browser default 16px). Dividing the original
Expand Down Expand Up @@ -129,7 +132,7 @@ code,
kbd {
font-size: 90%;
white-space: pre-wrap;
border-radius: 4px;
border-radius: var(--border-radius);
padding: 0.2em 0.4em;
background-color: var(--bg-secondary-color);
color: var(--color-error);
Expand Down Expand Up @@ -379,10 +382,10 @@ textarea,
textarea[type="text"] {
font-family: inherit;
padding: 0.5rem 0.625rem;
border-radius: 4px;
border-radius: var(--border-radius);
border: 1px solid var(--color-lightGrey);
font-size: 1em;
transition: all 0.2s ease;
transition: all var(--transition-duration) ease;
display: block;
width: 100%;
}
Expand All @@ -407,12 +410,12 @@ button {
padding: 0.625rem 1.5625rem;
color: var(--color-darkGrey);
background: var(--color-lightGrey);
border-radius: 4px;
border-radius: var(--border-radius);
border: 1px solid transparent;
font-size: var(--font-size);
line-height: 1;
text-align: center;
transition: opacity 0.2s ease;
transition: opacity var(--transition-duration) ease;
text-decoration: none;
transform: scale(1);
display: inline-block;
Expand Down Expand Up @@ -467,10 +470,10 @@ button:disabled:hover {
}
.grouped.gapless > *:first-child {
margin: 0 !important;
border-radius: 4px 0 0 4px !important;
border-radius: var(--border-radius) 0 0 var(--border-radius) !important;
}
.grouped.gapless > *:last-child {
border-radius: 0 4px 4px 0 !important;
border-radius: 0 var(--border-radius) var(--border-radius) 0 !important;
}
/* Responsive wrapping: enabled only on narrow viewports.
On desktop, grouped controls stay horizontal (existing behavior).
Expand Down Expand Up @@ -570,7 +573,7 @@ button:active:not(:disabled) {
transform: scale(0.98);
}
::placeholder {
color: #bdbfc4;
color: var(--color-placeholder);
}
.nav {
display: flex;
Expand Down Expand Up @@ -637,7 +640,7 @@ button:active:not(:disabled) {
}
.card {
padding: 0.625rem 1.25rem;
border-radius: 4px;
border-radius: var(--border-radius);
background: var(--bg-color);
box-shadow: 0 1px 3px var(--color-grey);
}
Expand Down
2 changes: 1 addition & 1 deletion dist/chota.min.css

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,10 @@ src/chota.css

Chota exposes CSS custom properties for theming. Override on `:root` or any selector:

- Color tokens: `--color-primary`, `--color-success`, `--color-error`, `--color-darkGrey`, `--color-grey`, `--color-lightGrey`, `--bg-color`, `--bg-secondary-color`, `--font-color`
- Color tokens: `--color-primary`, `--color-success`, `--color-error`, `--color-darkGrey`, `--color-grey`, `--color-lightGrey`, `--bg-color`, `--bg-secondary-color`, `--font-color`, `--color-placeholder`
- Spacing tokens: `--grid-gutter`, `--grid-maxWidth`
- Typography tokens: `--font-size`, `--font-family-sans`, `--font-family-mono`

**Note:** Border radius (hardcoded as `4px`) and transition duration (hardcoded as `0.2s`) are **not** exposed as CSS variables.
- Component tokens: `--border-radius`, `--transition-duration`

### Grid System

Expand Down
3 changes: 3 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ <h3 id="customizing">Customizing</h3>
--font-color: #333333;
--font-family-sans: sans-serif;
--font-family-mono: monaco, "Consolas", "Lucida Console", monospace;
--color-placeholder: #bdbfc4;
--border-radius: 4px;
--transition-duration: 0.2s;
}
{% endhighlight %}
<br>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"postcss": "postcss ./src/chota.css -d dist/",
"watch": "cross-env NODE_ENV=development postcss ./src/chota.css -d dist/ -w",
"test": "stylelint src/*.css",
"test:vrt": "npx playwright test test/vrt/index.spec.js test/vrt/components.spec.js test/vrt/grouped-controls.spec.js test/vrt/validations/mobile-grid-overflow.spec.js test/vrt/validations/nav-logo-full-height.spec.js test/vrt/validations/spacing-utilities.spec.js test/vrt/dark-mode.spec.js test/vrt/breakpoints.spec.js test/vrt/keyboard-focus.spec.js --project=chromium",
"test:vrt": "npx playwright test test/vrt/index.spec.js test/vrt/components.spec.js test/vrt/grouped-controls.spec.js test/vrt/validations/mobile-grid-overflow.spec.js test/vrt/validations/nav-logo-full-height.spec.js test/vrt/validations/spacing-utilities.spec.js test/vrt/dark-mode.spec.js test/vrt/breakpoints.spec.js test/vrt/keyboard-focus.spec.js test/vrt/token-overrides.spec.js --project=chromium",
"test:a11y": "npx playwright test test/vrt/a11y.spec.js --project=chromium",
"test:api": "npx playwright test test/vrt/api.spec.js --project=chromium",
"baselines": "node scripts/baselines.js"
Expand Down
5 changes: 4 additions & 1 deletion src/_base.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
"Avenir Next", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
"Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
--font-family-mono: monaco, "Consolas", "Lucida Console", monospace;
--color-placeholder: #bdbfc4;
--border-radius: 4px;
--transition-duration: 0.2s;
}

/* Chota does not set the root font-size. All rem values are relative to the
Expand Down Expand Up @@ -149,7 +152,7 @@ code,
kbd {
font-size: 90%;
white-space: pre-wrap;
border-radius: 4px;
border-radius: var(--border-radius);
padding: 0.2em 0.4em;
background-color: var(--bg-secondary-color);
color: var(--color-error);
Expand Down
2 changes: 1 addition & 1 deletion src/_card.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.card {
padding: 0.625rem 1.25rem;
border-radius: 4px;
border-radius: var(--border-radius);
background: var(--bg-color);
box-shadow: 0 1px 3px var(--color-grey);
}
Expand Down
14 changes: 7 additions & 7 deletions src/_form.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ textarea,
textarea[type="text"] {
font-family: inherit;
padding: 0.5rem 0.625rem;
border-radius: 4px;
border-radius: var(--border-radius);
border: 1px solid var(--color-lightGrey);
font-size: 1em;
transition: all 0.2s ease;
transition: all var(--transition-duration) ease;
display: block;
width: 100%;
}
Expand Down Expand Up @@ -45,12 +45,12 @@ button {
padding: 0.625rem 1.5625rem;
color: var(--color-darkGrey);
background: var(--color-lightGrey);
border-radius: 4px;
border-radius: var(--border-radius);
border: 1px solid transparent;
font-size: var(--font-size);
line-height: 1;
text-align: center;
transition: opacity 0.2s ease;
transition: opacity var(--transition-duration) ease;
text-decoration: none;
transform: scale(1);
display: inline-block;
Expand Down Expand Up @@ -112,11 +112,11 @@ button:disabled:hover {

.grouped.gapless > *:first-child {
margin: 0 !important;
border-radius: 4px 0 0 4px !important;
border-radius: var(--border-radius) 0 0 var(--border-radius) !important;
}

.grouped.gapless > *:last-child {
border-radius: 0 4px 4px 0 !important;
border-radius: 0 var(--border-radius) var(--border-radius) 0 !important;
}

/* Responsive wrapping: enabled only on narrow viewports.
Expand Down Expand Up @@ -238,5 +238,5 @@ button:active:not(:disabled) {
}

::placeholder {
color: #bdbfc4;
color: var(--color-placeholder);
}
24 changes: 24 additions & 0 deletions test/token-overrides.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Chota token overrides</title>
<link rel="stylesheet" href="/dist/chota.css">
<style>
:root {
--border-radius: 0px;
--transition-duration: 0.5s;
--color-placeholder: #ff0000;
}
</style>
</head>
<body>
<div class="card" data-test-id="card">card</div>
<button class="button" data-test-id="button">button</button>
<input type="text" placeholder="test" data-test-id="input">
<div class="grouped gapless">
<button class="button" data-test-id="grouped-first">first</button>
<button class="button" data-test-id="grouped-last">last</button>
</div>
</body>
</html>
5 changes: 4 additions & 1 deletion test/vrt/api-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
"--font-size",
"--font-color",
"--font-family-sans",
"--font-family-mono"
"--font-family-mono",
"--color-placeholder",
"--border-radius",
"--transition-duration"
],
"selectors": [
":root",
Expand Down
77 changes: 77 additions & 0 deletions test/vrt/token-overrides.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const { test, expect } = require('@playwright/test');
const { startFixtureServer, loadFixture } = require('./vrt-helpers');

const DESKTOP = { width: 1280, height: 720 };

let server;
let PORT;

test.beforeAll(async () => {
// Use port 3601 to avoid conflicts with other VRT tests.
// (breakpoints.spec.js already uses 3600; 3100/3200/3201/3301/3400/3500/3700/3800 also taken.)
server = await startFixtureServer(3601);
PORT = server.port;
});

test.afterAll(async () => {
if (server) await server.stop();
});

/**
* Computed-style spec for the three design tokens added in card #138
* (--color-placeholder, --border-radius, --transition-duration).
*
* The fixture (test/token-overrides.html) overrides all three tokens on
* :root; this spec proves the override propagates to every consumer:
* .card, .button, inputs, ::placeholder, and .grouped.gapless outer corners.
*/
test.describe('Design token overrides (#138)', () => {
test('token overrides propagate to all consumers', async ({ page }) => {
await page.setViewportSize(DESKTOP);
await loadFixture(page, server, 'token-overrides.html');

// 1. .card border-radius === 0px (--border-radius: 0px)
const cardRadius = await page.evaluate(() => {
const el = document.querySelector('[data-test-id="card"]');
return window.getComputedStyle(el).borderRadius;
});
expect(cardRadius).toBe('0px');

// 2. .button border-radius === 0px
const buttonRadius = await page.evaluate(() => {
const el = document.querySelector('[data-test-id="button"]');
return window.getComputedStyle(el).borderRadius;
});
expect(buttonRadius).toBe('0px');

// 3. .button transition-duration === 0.5s (--transition-duration: 0.5s)
const buttonDuration = await page.evaluate(() => {
const el = document.querySelector('[data-test-id="button"]');
return window.getComputedStyle(el).transitionDuration;
});
expect(buttonDuration).toBe('0.5s');

// 4. input ::placeholder color === rgb(255, 0, 0) (--color-placeholder: #ff0000)
const placeholderColor = await page.evaluate(() => {
const el = document.querySelector('[data-test-id="input"]');
return window.getComputedStyle(el, '::placeholder').color;
});
expect(placeholderColor).toBe('rgb(255, 0, 0)');

// 5. .grouped.gapless > *:first-child border-radius === 0px
// (all corners squared because --border-radius: 0px;
// Chromium serializes four equal longhands to a single value per CSSOM)
const groupedFirstRadius = await page.evaluate(() => {
const el = document.querySelector('[data-test-id="grouped-first"]');
return window.getComputedStyle(el).borderRadius;
});
expect(groupedFirstRadius).toBe('0px');

// 6. .grouped.gapless > *:last-child border-radius === 0px
const groupedLastRadius = await page.evaluate(() => {
const el = document.querySelector('[data-test-id="grouped-last"]');
return window.getComputedStyle(el).borderRadius;
});
expect(groupedLastRadius).toBe('0px');
});
});
Loading