Skip to content

feat(design): M2 design tokens (warm neutral + ink, Instrument Sans / IBM Plex Sans Arabic) - #11

Merged
AdilAzhariOmsan merged 5 commits into
mainfrom
feat/design-tokens
Nov 8, 2026
Merged

AdilAzhariOmsan merged 5 commits into
mainfrom
feat/design-tokens

Conversation

@AdilAzhariOmsan

@AdilAzhariOmsan AdilAzhariOmsan commented Nov 8, 2026 •

Copy link
Copy Markdown
Collaborator

M2 design tokens: warm neutral palette with an ink accent, Instrument Sans / IBM Plex Sans Arabic, mapped into Tailwind 4. Contrast is enforced by tests, and a token gallery is available only when APP_ENV=local. No new dependencies.

Merge condition: this PR merges only when both PHP 8.5 / Laravel 13 and Node 24 / Vue 3 are green on the final head commit (follow-up F-1 from #10).

Summary

Five commits on main (316b311):

  1. feat(design): design tokens with a tested contrast floor
  2. feat(design): local-only design token gallery
  3. style(storefront): the home placeholder uses the tokens
  4. docs(engineering): token rules, plus the start-of-milestone npm audit reassessment
  5. test(design): body text on status backgrounds (review finding F1)

Related Issues

N/A

Requirement

M2 (design tokens), per the Phase 3 milestone plan. NFR-A11Y-03 (contrast ≥ 4.5:1), ADR-0020 (Filament reuses the tokens), ADR-0018 §3 (Arabic uses Latin digits).

Type of Change

  • Feature
  • Tests
  • Documentation

What Changed

  • resources/css/tokens.css: plain CSS custom properties (--nx-*) with no Tailwind or Vue syntax, so Filament can import them unchanged.

    • Two layers: --nx-palette-* (raw values) and semantic tokens (--nx-color-bg-*, fg-*, accent*, border-*, focus-ring, {success,warning,danger,info}-{fg,bg}).
    • Also covers the type scale and leading, weights, tracking, a 4px spacing unit plus gutter/section/content width, radius, elevation (soft shadows tinted with the ink colour), and durations and easings.
    • Reduced motion sets every duration to 0ms.
    • Light only. Dark mode will be one block that re-points the semantic colours, with no component changes.
  • Owner decisions applied: premium minimalist, warm neutrals, a near-black ink accent with no hue (colour is reserved for status), and Instrument Sans + IBM Plex Sans Arabic.

  • Fonts:

    • Both are self-hosted through the existing laravel-vite-plugin Bunny integration.
    • Plex Arabic is arabic-subset only and preload: false.
    • :lang(ar) switches the stack (Latin words and digits stay in Instrument Sans) and lengthens the body line height. It is scoped by language rather than the page locale, so Arabic content inside an English page gets it too.
  • app.css: @theme inline maps the tokens. Tailwind's default colours, radii, shadows, fonts, type scale and tracking are cleared, so bg-red-500 and similar classes no longer exist. Adds base canvas/text colours and a :focus-visible outline from the focus-ring token.

  • Contrast: resources/js/design/contrast-pairs.ts derives the required pairs from token names:

    • text on every background at 4.5:1;
    • status fg on its own bg and on every background at 4.5:1;
    • every text token on every status bg at 4.5:1 (body text inside alerts and badges);
    • on-accent on accent and accent-hover at 4.5:1;
    • accent, border-control and focus-ring on every background at 3:1 (WCAG 1.4.11).

    A coverage test fails on any colour token that is neither checked nor listed in DECORATIVE_COLOR_TOKENS with a reason. The WCAG maths (contrast.ts) and the token parser (tokens.ts) are first-party and unit tested.

  • Gallery:

    • routes/local.php (/dev/tokens, Dev/Tokens) is loaded from bootstrap/app.php only when APP_ENV=local.
    • The page reads tokens.css?raw itself, so it cannot drift from the stylesheet.
    • It shows colour swatches, the palette, the contrast table, the type scale, en/ms/ar samples, spacing, radius, elevation and motion.
  • vitest.config.ts: lets tests import resources/css/*.css as ?raw text. By default Vitest stubs CSS, so a raw import is an empty string.

  • Docs: code-quality.md gains a "Styling — design tokens" section. security.md reassessment dates are updated to 2026-11-08 after the full npm audit, which found the same two dev-only advisories and nothing new.

Design / Architecture

  • Tokens as plain CSS, with Tailwind as a consumer. This keeps ADR-0020 open: Filament can theme from tokens.css without Tailwind.
  • @theme inline makes utilities reference the token variables at runtime, so the :lang(ar) and reduced-motion overrides apply without separate builds.
  • Clearing the Tailwind defaults is deliberate: a palette class can't be used by accident, and every colour goes through a token that the contrast test covers.
  • Gallery gating by route registration, not middleware: outside local the route does not exist and 404s like any unknown URL. The page chunk (Tokens-*.js, 5.4 kB gzip, token names only) is still built into production assets, but nothing routes to it.

Testing

  • Unit tests (Vitest: WCAG maths, token parser, contrast pairs and coverage, gallery component)
  • Feature/integration tests (Pest: gallery 404 per environment, renders in local)
  • Architecture tests (existing suite)
  • Manual verification (Chrome)

Test Evidence

Local, Node 24.22.2 / npm 11.19.1, PHP 8.5, with APP_CONFIG_CACHE pointed at a path that doesn't exist, from a clean node_modules / public/build:

Step Result
npm ci ok
npm run build ok
composer check composer.json valid · 0 advisories · Pint passed · PHPStan 0 errors · Pest 13 passed (47 assertions)
npm run typecheck ok
npm test 68 passed (5 files)
npm audit --omit=dev 0 vulnerabilities
AC Evidence
Tokens for colour, type, spacing, radius, elevation and motion, mapped into Tailwind 4 @theme tokens.css (88 tokens) and app.css; the built CSS resolves font-sans, bg-canvas and others to --nx-*
Contrast ≥ 4.5:1 for every text/background pair 50 pairs pass. The lowest text pair is success-fg on bg-muted at 5.64:1; the lowest non-text pair is border-control on bg-muted at 3.84:1 (needs 3:1)
The test really guards Swapping fg-muted to neutral-500 fails 4 pairs; adding an unclassified --nx-color-new-thing fails the coverage test. Darkening danger-bg fails the new fg-muted on danger-bg pair. All reverted
Gallery only in APP_ENV=local Pest boots a fresh app per environment: production, staging, demo and testing → 404; local → 200, component Dev/Tokens
Arabic rendering In Chrome, article[lang=ar] computes font-family: "IBM Plex Sans Arabic", "Instrument Sans", …, line-height 28px vs 24px for en; the Plex Arabic woff2 files load; dir=rtl
Bundle budget Storefront app-*.js 62.80 kB gzip (≤ 200 kB); CSS 5.01 kB gzip; Plex Arabic is not preloaded

Security / Data Isolation

  • No cross-vendor data leakage introduced (no vendor or auth code)
  • Sensitive data handling reviewed: the gallery exposes only token names and values, and only in local
  • Dev-only npm advisories are unchanged (GHSA-3g3m-2fjr-6pw4, GHSA-g3hv-75wg-m249) and were reassessed on 2026-11-08

Database / Migration

  • No database changes

Screenshots / Recording

Run npm run build && php artisan serve, then open /dev/tokens with APP_ENV=local. The home page is now the token-styled placeholder. Screenshots can be added on request.

Documentation

  • Documentation updated (docs/engineering/code-quality.md, docs/engineering/security.md)

Reviewer Notes

  • Focus:
    • The token values and naming in tokens.css.
    • The @theme inline reset block in app.css.
    • The pair rules in contrast-pairs.ts.
    • The then: gate in bootstrap/app.php.
  • Known items:
    • No logo yet; that is owner input.
    • The existing fontaine build warning from bunny optimizedFallbacks is unchanged.
    • The Plex Arabic CSS variable from the plugin is the bare family name; metric-matched fallbacks come from the plugin's generated @font-face rules.
  • Follow-ups:

Definition of Done

  • Acceptance criteria are satisfied
  • composer check passes locally (validate, audit, Pint, PHPStan, Pest)
  • No unrelated changes
  • No secrets committed
  • Self-review completed
  • Reviewer feedback addressed

- resources/css/tokens.css: plain CSS custom properties (--nx-*) for
  colour, type, spacing, radius, elevation and motion. No Tailwind or
  Vue syntax, so the Filament admin can import it unchanged (ADR-0020).
  Two layers: --nx-palette-* raw values and semantic tokens. Light only;
  dark mode is a later block that re-points the semantic colours.
- Direction (owner decision): premium minimalist, warm neutrals, an ink
  accent with no hue; colour is reserved for status.
- Type: Instrument Sans (Latin and Malay) and IBM Plex Sans Arabic,
  both self-hosted through the existing laravel-vite-plugin Bunny fonts
  integration. Arabic is arabic-subset only and not preloaded. :lang(ar)
  switches the stack and lengthens body line heights.
- app.css maps the tokens into Tailwind 4 with @theme inline and clears
  the default colours, radii, shadows, fonts, type scale and tracking,
  so every style goes through a token.
- Reduced motion sets every duration token to 0ms.
- Contrast (NFR-A11Y-03): contrast-pairs.ts derives the required pairs
  from token names (text 4.5:1; control borders, focus ring and accent
  3:1) and a coverage test fails on any colour token that is neither
  checked nor listed as decorative. WCAG maths and token parsing are
  first-party (no new dependencies) and unit tested.
- vitest.config.ts lets tests import resources/css as ?raw text.
/dev/tokens renders every token read from tokens.css itself: semantic
colours and palette, the full contrast table with ratios, the type
scale, en/ms/ar samples (Arabic right to left), spacing, radius,
elevation and motion.

routes/local.php is loaded from bootstrap/app.php only when
APP_ENV=local, so the route does not exist in testing, staging, demo or
production. The Pest test boots a fresh application per environment to
exercise the real gate.
- code-quality.md: token rules (source of truth, cleared Tailwind
  defaults, semantic-only use, contrast test, local gallery, Arabic).
- security.md: start-of-milestone full npm audit for M2 found the same
  two dev-only advisories; last-reassessed dates updated.
Review finding F1: status backgrounds were only paired with their own
status foreground, so body text inside a tinted alert or badge (e.g. a
danger message in fg-muted) was never checked. Every fg-* token is now
paired with every {tone}-bg at 4.5:1 (8 new pairs, 50 in total), and a
test asserts those pairs exist.

Current values: fg-default about 15.3:1 and fg-muted about 6.2:1 on
every status background. Darkening danger-bg now fails the new
fg-muted pair.

@AdilAzhariOmsan AdilAzhariOmsan left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@AdilAzhariOmsan
AdilAzhariOmsan merged commit c109eba into main Nov 8, 2026
2 checks passed
@AdilAzhariOmsan
AdilAzhariOmsan deleted the feat/design-tokens branch November 8, 2026 09:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant