Skip to content

Releases: willink-oss/willink-design-system

v1.8.0 — catalog expansion (14 components) + @layer extension surface

19 Jun 12:21

Choose a tag to compare

npm lockstep 1.7.0 → 1.8.0 · @willink-labs/{tokens,tailwind-preset,css-tokens,react} · published to npmjs.org via OIDC Trusted Publisher (with SLSA provenance). Flutter willink_theme stays 1.5.0 (independent cadence). Roadmap milestone v1.9 (extensibility).

Highlights

  • 14 new React components (25 → 39) — Popover, ScrollArea, Spinner, Empty, Kbd, ButtonGroup, Alert, Table, Pagination, Breadcrumb, ContextMenu, HoverCard, Collapsible, ToggleGroup. Each ships a jest-axe test, a Storybook story, and an a11y-matrix row.
  • @layer components extension surface (.wl-btn / .wl-card / .wl-input, ADR-0021 §3) — framework-agnostic component classes for non-React consumers, overridable with atomics.
  • Reduced-motion contract completed (ADR-0008 Layer 1) on DropdownMenu/Select + a check-motion-contract regression gate.
  • Packaging: homepage + bugs metadata on all 4 packages.

Install

npm i @willink-labs/react@1.8.0 @willink-labs/tailwind-preset@1.8.0 @willink-labs/tokens@1.8.0 @willink-labs/css-tokens@1.8.0

Out of scope

  • willink_theme (Flutter / pub.dev) — unchanged at 1.5.0.
  • apps/registry — scaffolded (#93), not yet hosted; no consumer-facing change.

Verification

Layer 2 (ADR-0012): version × CHANGELOG × README agree; full local gate green (build / test / guardrails / contrast); tree-parity verified against the gated commit; published packages carry provenance attestation.

Related: ADR-0008 · ADR-0010 · ADR-0021 · roadmap/v1.9

v1.7.0 — dark-aware gradient text + bg-clip-text contrast gate

13 Jun 09:10

Choose a tag to compare

Lockstep npm MINOR 1.6.0 → 1.7.0 (@willink-labs/tokens · tailwind-preset · react · css-tokens). No Flutter release (willink_theme stays 1.5.0, ADR-0011).

Decisions: ADR-0018 · cycle v1.8 roadmap. PR #61.

What shipped

Dark-aware text-gradient-primary (tailwind-preset, real content). The bg-clip-text gradient heading painted its glyphs with the fixed brand → brand-glow pair (brand-600 → brand-500), which does not flip (ADR-0013) — so on the dark bg the worst endpoint rendered at 3.54:1, below AA, washing out. New preset-internal --color-gradient-primary-from/-to make the endpoints dark-aware: light byte-identical (brandbrand-glow, worst 4.23:1), dark brand-300brand-400 (worst 7.41:1), staying recognizably brand-purple. bg-gradient-primary / bg-gradient-ai unchanged (vivid bg behind white text).

Closed the audit blind spot the CEO caught twice. scripts/check-contrast.mjs gained a TEXT_GRADIENTS registry that checks every text-clipped gradient's worst endpoint against bg — required ≥ 4.5 in dark, report-only baseline in light — wired into CI via the existing tokens contrast gate. A deliberately-bad endpoint (the pre-1.7 fixed pair) turns the gate red (3.54:1 ✗, exit 1). bg-clip-text gradient headings are now first-class contrast-gate citizens.

Consumer contract for accent-on-gradient text. New docs/a11y/gradient-and-accent.md documents the rule the DS can't enforce (hue/lightness separation for custom accent text on gradient backgrounds — the i-willink.com hero blind spot).

tokens / css-tokens / react carry lockstep markers (the gradient vars are preset-internal — not semantic.json / css-tokens output / component refs, mirroring --color-gradient-subtle-end). reduced-motion behavior unaffected.

🤖 Generated with Claude Code

v1.6.0 — dark-mode link contrast fix (#58) + info-fg defer (#59)

13 Jun 06:17

Choose a tag to compare

Lockstep MINOR — 4 npm packages → 1.6.0 (@willink-labs/tokens · tailwind-preset · react · css-tokens). No Flutter release this cycle (willink_theme stays 1.5.0 — independent cadence, ADR-0011).

Button link dark contrast fix (#58)

Button variant="link" resting color used text-brand — the fixed brand-600 primitive, which is mode-invariant (ADR-0013) — so on the dark page background it rendered 3.54:1, below WCAG AA. Reproduced by the i-willink.com production sweep on /design-system. Re-pointed to the flipping text-brand-soft-fg (light brand-700 7.10:1 / dark brand-300 10.93:1 — both AAA). Hover (text-brand-hover) already flips and stays legible (dark 4.76:1).

The same bug class (fixed brand foreground on a flipping surface) was audited DS-wide: AccordionTrigger hover:text-brandhover:text-brand-hover (also fixed); RadioGroupItem text-brand audited and kept (8px indicator dot is a graphical object under WCAG 1.4.11's 3:1 rule, which it clears). The contrast gate gained two required pairs (brand-soft-fg/bg, brand-hover/bg) in both modes.

Light-mode resting links are now one ramp step darker (brand-600 → brand-700) — a deliberate, contrast-improving MINOR default-visual change (ADR-0010). No migration required.

info-fg upstreaming verdict (#59) — deferred

Evaluated i-willink.com's local --color-info-fg (blue) for upstreaming. Verdict NO this cycle: single usage site, no info hue/surface family in the DS for it to belong to, below the ADR-0016 promote-on-reproduction bar. #59 left open with the reasoning as a comment — a defer, not a reject.

Decisions

ADR-0017 — Button link fix rationale (both fix options weighed) + info-fg defer verdict. Roadmap: docs/roadmap/v1.7.md.

Packages

Real content in @willink-labs/react; tokens / tailwind-preset / css-tokens carry lockstep markers (the fix reuses existing roles — no new token, variable, safelist entry, or generated var).

v1.5.0 — fg-* text-emphasis roles

13 Jun 02:51

Choose a tag to compare

v1.5.0 — fg-* text-emphasis roles

Upstreams the foreground emphasis ladder i-willink.com grew during its dark rollout (v1.5 Outcome 3) into the Design System as official semantic roles. The DS previously shipped only fg (strongest body) and muted (weakest supporting) — with no in-between steps, consumers reached into mode-invariant text-neutral-* for emphasis, which froze light grays under a dark root. These five roles close that gap and flip in dark mode like every other semantic role.

New roles (between fg and muted)

role utility light dark contrast contract (on bg)
fg-strong text-fg-strong neutral-800 neutral-100 ≥ 7 (AAA) — headings / strong runs
fg-emphasis text-fg-emphasis neutral-700 neutral-200 ≥ 7 (AAA) — labels / links
fg-secondary text-fg-secondary neutral-600 neutral-300 ≥ 4.5 (AA) — secondary body
fg-subtle text-fg-subtle neutral-400 neutral-500 documented baseline — captions / meta / placeholders (non-body)
fg-faint text-fg-faint neutral-300 neutral-600 documented baseline — disabled text / separators (non-text)

fg and muted are unchanged (the ladder's anchors). Each role is a neutral-step alias + a willink.dark extension, so it rides the existing ADR-0013 dark-flip + consumer-override machinery with no new mechanism.

Packages (npm lockstep 1.4.1 → 1.5.0)

  • @willink-labs/tokens@1.5.0 — 5 new semantic roles
  • @willink-labs/tailwind-preset@1.5.0 — light decls + both dark blocks + text-fg-* safelist
  • @willink-labs/css-tokens@1.5.0 — regen (5 vars in tokens.css/tokens.semantic.css, flipped in tokens.dark.css)
  • @willink-labs/react@1.5.0 — lockstep marker (no component change)

No flutter-v* tag — zero Flutter changes (willink_theme stays 1.5.0 per ADR-0011).

pnpm add @willink-labs/tokens@1.5.0 @willink-labs/tailwind-preset@1.5.0 @willink-labs/react@1.5.0 @willink-labs/css-tokens@1.5.0

For consumers

Replace local fg-* definitions (or text-neutral-* used for body emphasis) with the new text-fg-* utilities — they render byte-identical in light (the DS neutral scale is the slate scale) and gain a correct dark flip. A separate task migrates i-willink.com's local fg-* block to these roles.

Contrast policy

scripts/check-contrast.mjs enforces the per-role targets in both modes: fg-strong/fg-emphasis ≥ 7 (AAA) and fg-secondary ≥ 4.5 (AA) are required; fg-subtle/fg-faint are documented report-only baselines below the body-text floor (non-body tiers — not for content text).

Out of scope (v1.7)

Pastel-chip semantic pass, naskit diagram theming, theme-toggle UX (all i-willink.com consumer-side follow-ups).

Links

v1.4.1 — react dist ships 'use client' (RSC fix)

12 Jun 00:02

Choose a tag to compare

v1.4.1 — react dist ships 'use client' (RSC fix)

Lockstep PATCH cut for the npm group. Production-discovered in the clublink-platform rollout: @willink-labs/react@1.4.0 executes client-only code at module top level (createContext in FormField, hooks throughout) but the published dist carried no 'use client' directive — esbuild strips source-level directives during bundling — so next build crashed for any RSC consumer importing from a Server Component.

Consumers on Next.js App Router can now delete their client re-export shims and import @willink-labs/react directly from Server Components again (components remain Client Components, as before). No API or type change — the 1.4.1 index.d.ts is byte-identical to 1.4.0.

Packages

Package Version Content
@willink-labs/react 1.4.1 dist leads with 'use client' (tsup banner; rollup treeshake pass off — it deleted directives; ~4.7 KB dist cost) + build-output regression check
@willink-labs/tokens 1.4.1 lockstep marker (no source change)
@willink-labs/tailwind-preset 1.4.1 lockstep marker (no source change)
@willink-labs/css-tokens 1.4.1 lockstep marker (no source change)

No flutter-v* tag this cycle — willink_theme stays at 1.5.0 (ADR-0011).

Install

pnpm add @willink-labs/react@^1.4.1 @willink-labs/tailwind-preset@^1.4.1 @willink-labs/tokens@^1.4.1

Regression guard

packages/react/scripts/check-dist-use-client.mjs runs inside pnpm build (CI gate per ADR-0012 Layer 0) and asserts every dist JS file starts with the directive — position matters for RSC, so it checks the first bytes rather than grepping for containment.

Verification

ADR-0012 Layer 0–2: full local gate at the cut (guardrails / 194 unit tests / pnpm -r build incl. playground + storybook / audit / contrast / flutter analyze + 66 tests + dry-run) / d.ts diff vs published 1.4.0 = identical (PATCH proof) / published-tarball smoke test: 'use client'; at byte 0 of dist/index.js, exactly 1 occurrence, SLSA provenance attested, workspace:* peerDeps resolved to 1.4.1 / npm view ×4 == 1.4.1. PR: #53.

v1.4.0 — FormField compound (npm lockstep)

11 Jun 12:42

Choose a tag to compare

v1.4.0 — FormField compound

Lockstep MINOR cut for the npm group. Closes the longest-deferred React roadmap item (deferred at the v1.0 Phase 9.1 audit, carried through v1.1–v1.3): a FormField compound that automates the id / htmlFor / aria-describedby / aria-invalid wiring consumers previously hand-wrote per field.

Packages

Package Version Content
@willink-labs/react 1.4.0 FormField compound — 25th component
@willink-labs/tailwind-preset 1.4.0 text-danger safelist entry (FormFieldError class string)
@willink-labs/tokens 1.4.0 lockstep marker (no source change)
@willink-labs/css-tokens 1.4.0 lockstep marker (no source change)

No flutter-v* tag this cycle — willink_theme stays at 1.5.0 (ADR-0011).

API

import { FormField, FormFieldControl, FormFieldDescription, FormFieldError, FormFieldLabel, Input } from "@willink-labs/react";

<FormField>
  <FormFieldLabel required>Email</FormFieldLabel>
  <FormFieldControl>
    <Input type="email" />
  </FormFieldControl>
  <FormFieldDescription>会社のメールアドレスを入力してください。</FormFieldDescription>
  <FormFieldError>{errors.email}</FormFieldError>
</FormField>
  • ids generated via useId() (collision-free in lists); label htmlFor pre-wired
  • FormFieldControl is a Radix Slot (zero new runtime deps) — injects id + merged aria-describedby + aria-invalid onto any single control (Input / Textarea / native elements / Radix triggers)
  • aria-describedby only ever references rendered nodes (description/error detected among direct children, SSR-safe)
  • FormFieldError renders only with content, carries role="alert", and drives the control's aria-invalid automatically (invalid prop on FormField as override)
  • Exported types: FormFieldProps / FormFieldLabelProps / FormFieldControlProps / FormFieldDescriptionProps / FormFieldErrorProps

Design rationale (incl. what was deliberately left out — no CVA, no validation/RHF coupling, no layout props): ADR-0015. Cycle doc: docs/roadmap/v1.4.md.

Install

pnpm add @willink-labs/react@^1.4.0 @willink-labs/tailwind-preset@^1.4.0 @willink-labs/tokens@^1.4.0

Out of scope (v1.5 candidates)

Dark-mode consumer rollout / Flutter CheckboxSwitchRadioGroup wrappers / PopoverComboboxBreadcrumbStepper (Discussion-gated) — see the v1.4 roadmap.

Verification

ADR-0012 Layer 0–2: 171 unit tests (12 new, incl. jest-axe) / Storybook addon-a11y story surface / full local gate before tagging (guardrails・build・audit・contrast・flutter analyze+test+dry-run) / post-publish npm view ×4 == 1.4.0 with OIDC provenance.

v1.3.0 — WordPress consumption (4 npm packages lockstep)

11 Jun 10:51

Choose a tag to compare

@willink-labs/{tokens, tailwind-preset, css-tokens, react} 1.3.0

WordPress consumption ships — and the "WordPress package" roadmap item closes without a package. Two pilots proved @willink-labs/css-tokens is the WordPress integration: wp-modern-starter-kit (canonical wiring pattern) and esperanza-wp-theme (production — 10/10 variable parity, byte-identical dist except inert variable additions, 4/4 visual regression; consumer-side checks ran in that repo). Decision record: ADR-0014.

  • css-tokens — both pilot findings fixed:
    • Root-level proxy CSS files (tokens.css / tokens.scale.css / tokens.semantic.css / tokens.dark.css / tokens.primitives.css): plain-path resolvers — postcss-import, and therefore the Tailwind v3 CLI and classic WP PostCSS toolchains — never read the package exports map, so the documented specifiers were unresolvable (the pilots' BLOCKER). Every documented path is now a physical file; the exports map stays, plus a ./src/*.css passthrough keeps the pilots' workaround imports valid.
    • New export tokens.primitives.css — color-free primitives: radius + duration + easing only (10 vars, zero --color-* / --shadow-*). For consumers that keep their own palette and adopt only the DS shape/motion contract — esperanza's radius/motion-only contract no longer ingests 50 inert color/shadow vars.
  • tokens / tailwind-preset / react — lockstep markers, no source change.

Install

pnpm add @willink-labs/css-tokens@^1.3.0
# WordPress / PostCSS toolchains can now use the documented plain paths:
#   @import "@willink-labs/css-tokens/tokens.css";
#   @import "@willink-labs/css-tokens/tokens.primitives.css";

All four packages published via npm OIDC Trusted Publisher with --provenance (SLSA v1). No Flutter release this cycle — willink_theme stays at 1.5.0 (ADR-0011).

Out of v1.3 scope (v1.4 candidates)

FormField compound export (strongest candidate) · Flutter Checkbox/Switch/RadioGroup wrappers · Popover / Combobox / Breadcrumb / Stepper (Discussion-gated).

Roadmap: docs/roadmap/v1.3.md (PR #50).

v1.2.0 — Dark mode (4 npm packages lockstep)

11 Jun 01:40

Choose a tag to compare

@willink-labs/{tokens, tailwind-preset, css-tokens, react} 1.2.0

Dark mode ships. Auto via prefers-color-scheme; explicit control with <html data-theme="dark"> / "light". Semantic tokens flip, primitives and the numeric brand scale stay invariant — components need zero dark: variants. Design record: ADR-0013.

  • tokenswillink.dark DTCG $extensions on all 16 flipping roles; 5 new surface roles (surface-subtle/muted, track, surface-inverted(-fg)); WCAG contrast audit (scripts/check-contrast.mjs) now a permanent CI gate — dark pairs pass at 5.4–19.3:1.
  • tailwind-preset — full dark flip in both selector paths (media + data-theme), dark shadows, dark-aware bg-gradient-subtle; animate-pulse joins the prefers-reduced-motion safety net (closes the Skeleton flag carried since 0.13.0).
  • css-tokens — new tokens.dark.css export for non-Tailwind consumers.
  • react — 15 primitive-utility leaks across 11 components migrated to the new semantic roles; light-mode rendering is pixel-identical. Skeleton gains motion-reduce:animate-none. Regression guard added to the token-name test gate.

Storybook now has a light / dark / auto toolbar toggle. Roadmap: docs/roadmap/v1.2.md (PRs #43#48).

willink_theme 1.5.0 — WillinkTheme.willinkDark()

11 Jun 01:40

Choose a tag to compare

willink_theme 1.5.0

Dark mode for Flutter — WillinkTheme.willinkDark() mirrors the web semantic flips (ADR-0013) onto a manual Brightness.dark ColorScheme (surface ladder from neutral-950/900/800/700, brand-600 invariant, containers brand-950/300, error red-500). A shared _base() builder structurally guarantees light/dark slot parity. All 9 components adapt with no changes — verified by tests rendering Button / SnackBar / TabBar under the dark theme (66/66).

MaterialApp(
  theme: WillinkTheme.willink(),
  darkTheme: WillinkTheme.willinkDark(),
  themeMode: ThemeMode.system,
)

v1.1.0 — Storybook catalog + sonner 2 migration (4 npm packages lockstep)

10 Jun 23:09

Choose a tag to compare

@willink-labs/{tokens, tailwind-preset, css-tokens, react} 1.1.0

First post-freeze MINOR. Strict SemVer per ADR-0010^1.0.0 consumers update safely.

@willink-labs/react (the substantive package this cut)

  • Toast engine: sonner 1.7.4 → 2.0.7 (#30) — the toast type is unchanged; Toaster keeps the two props sonner 2.x removed as deprecated compat props (loadingIcon mapped onto icons.loading; pauseWhenPageIsHidden now a documented no-op). Classified MINOR; details in the CHANGELOG.
  • Slider a11y fix (#38) — single-thumb Slider now forwards aria-label to the thumb (axe aria-input-field-name); multi-thumb ranges keep Radix "Minimum"/"Maximum". Found by the new Storybook a11y pass.

tokens / tailwind-preset / css-tokens

Lockstep markers — no source change.

Also in this cycle (repo, not packages)