Skip to content

feat(ui): polish — design tokens, focus-visible, a11y, Dashboard component#237

Open
wreiske wants to merge 2 commits into
mainfrom
feat/ui-polish-phase1-4
Open

feat(ui): polish — design tokens, focus-visible, a11y, Dashboard component#237
wreiske wants to merge 2 commits into
mainfrom
feat/ui-polish-phase1-4

Conversation

@wreiske
Copy link
Copy Markdown
Member

@wreiske wreiske commented May 19, 2026

Summary

A focused polish sprint covering the four phases of the original UI polish plan: design-token foundation, semantic color cleanup, focus-visible standardization, accessibility instrumentation, and a new composable Dashboard component. Existing visual baselines remain valid (all 20 snapshot tests pass within tolerance), so this is a non-breaking visual upgrade that ships new capability (better dark mode, density support, motion tokens, brand-driven focus rings) without disturbing pixel-level appearance of existing components.

What's in it

Phase 1 — Design token foundation

  • Extended BrandConfig with optional spacing, density, motion, focusRing groups + numbered elevation 1..6 + inner (src/brands/types.ts)
  • New --mieweb-* CSS custom properties emitted into :root via src/styles/base.css so all 8 brands inherit defaults automatically — individual brands can opt-in to override
  • bluehive brand fully populated as the reference (src/brands/bluehive.{ts,css})
  • Tailwind preset (src/tailwind-preset.ts) exposes new utilities: shadow-elevation-1..6, spacing brand-xs..2xl (density-multiplied), transition duration fast/base/slow, transition timing standard/emphasized/decelerate, ring-width-focus, ring-offset-width-focus
  • Tailwind 4 @theme block re-exports the same tokens
  • Density: preferred selector [data-density="compact"] with legacy body.condensed alias for the existing 11.5k-line condensed sheet
  • :focus-visible now consumes --mieweb-focus-ring-* so brands can configure focus appearance
  • prefers-reduced-motion already honored in base CSS

Phase 2 — Component cleanup

  • Badge — hardcoded green-*/yellow-*/red-* palettes → semantic success/warning/destructive scales (full light + dark mode coverage)
  • HRISProviderSelector pending-sync alert: yellow-* literals → warning semantic tokens, added role="status"
  • ProviderOverview skeleton: gray-200 dark:bg-gray-700bg-muted (theme-aware)
  • Global focus-visible sweep: focus:ring*focus-visible:ring* across all 54 affected component files — keyboard-only focus indication, no ring on mouse click
  • Table — added scope="col" on <th> for screen-reader column semantics (already had aria-sort)

Phase 3 — Dashboard

  • New composable <Dashboard> component (src/components/Dashboard/Dashboard.tsx) with slot API:
    • Dashboard.Header / .Title / .Subtitle / .Actions
    • Dashboard.Grid — responsive 1-col (mobile) → 6-col (sm) → 12-col (lg) CSS grid
    • Dashboard.Widget colSpan={1..12} smColSpan={1..6}
    • Uses gap-brand-md/lg density-aware spacing
  • Exported from src/index.ts
  • Cleaned up stale *.bak / *.backup / *.broken files

Phase 4 — Accessibility verification

  • Added @axe-core/playwright (dev)
  • New tests/visual/a11y.spec.ts — runs axe against WCAG 2.1 A+AA tags on 14 core stories (Button, Input, Select, Checkbox, Switch, Badge, Card, Alert, Progress, Avatar, Breadcrumb, Tabs, Toast)
  • All 14 stories scan clean — zero critical or serious violations

Validation

Check Result
pnpm typecheck
pnpm lint
pnpm test (vitest) ✅ 113/113
pnpm build (tsup) ✅ ESM + CJS + DTS
pnpm test:visual regression ✅ 20/20 within 5% tolerance
pnpm test:visual a11y ✅ 14/14 zero critical/serious WCAG

Before/after screenshots — yes, we have visual regression testing

The repository uses Playwright visual regression (tests/visual/components.spec.ts) with 20 baseline PNGs in tests/visual/components.spec.ts-snapshots/. Those baselines are the "before" — they were generated against main prior to this work. When pnpm test:visual runs in this PR, Playwright pixel-compares against those baselines:

  • All 20 passed within the configured 5% pixel tolerance, so the polish is visually non-breaking — the changes are token plumbing and behavior (focus-visible, semantic dark mode) rather than dramatic restyling
  • If any test had failed, Playwright would auto-generate side-by-side *-actual.png / *-diff.png artifacts in playwright-report/ for review
  • A full HTML diff report is regenerated on every run at playwright-report/index.html

To see "after" visual changes for the dark-mode + density paths (which the existing snapshots don't cover), follow-up work would parameterize the spec across viewports + light/dark + density (see Follow-ups).

Design decisions worth a review note

  1. Density runtime API — currently brand-default only via BrandDensity.default. A future <MieWebUIProvider density="compact"> could swap densities at runtime. Out of scope here.
  2. Focus ring color — defaults to --mieweb-ring (matches existing brand). focusRing.color field on BrandConfig lets brands override.
  3. Dashboard widget content — left as open children API rather than constraining to specific slot types, so apps can drop in Card, charts, custom widgets.
  4. Other 7 brand CSS files — not individually populated with new tokens; they inherit defaults from base.css. Each brand can override later as needed. This avoids churning 7 files for no immediate visual change.

Follow-ups (not blocking)

  • Populate per-brand spacing/density/motion/focus-ring values in mieweb, ccme, enterprise-health, ozwell, waggleline, webchart, default
  • Parameterize tests/visual/components.spec.ts across mobile/tablet/desktop viewports + light/dark themes
  • Extend ARIA additions to custom Select combobox variant, DateInput/DateRangePicker, and replace <div role="checkbox"> in CheckrIntegration.tsx with real <Checkbox>
  • Rewrite Dashboard.stories.tsx (2273 lines) using the new <Dashboard> slot API
  • Move legacy DashboardWidget into Dashboard/ folder

Files changed: 62

  • 6 token/foundation files
  • ~50 components touched by focus-visible sweep
  • 2 new files: Dashboard.tsx, a11y.spec.ts
  • 3 backup files removed

Phase 1 — Token foundation
- Add BrandSpacing/BrandDensity/BrandMotion/BrandFocusRing/BrandElevation interfaces (src/brands/types.ts)
- Emit new --mieweb-* CSS custom props for spacing (xs..2xl), density-scale, motion (fast/base/slow + 3 easings), focus-ring (width/offset/style), elevation (1..6 + inner)
- Wire defaults into base.css :root + @theme so all 8 brands inherit automatically
- BlueHive populated with full token values (src/brands/bluehive.{ts,css})
- Tailwind preset exposes shadow elevation-*, spacing brand-*, transition duration/timing, ring width/offset utilities
- :focus-visible consumes brand focus-ring tokens
- Compact density via [data-density=compact] (preferred) + legacy body.condensed alias
- prefers-reduced-motion already honored

Phase 2 — Component cleanup
- Badge: hardcoded green/yellow/red → semantic success/warning/destructive scales (dark-mode aware)
- HRISProviderSelector pending-sync alert: yellow-* → warning tokens + role=status
- ProviderOverview skeleton: gray-200/gray-700 → bg-muted (theme-aware)
- Global sweep: focus:ring → focus-visible:ring across all components (mouse focus no longer shows ring)
- Table: <th scope=col> for screen-reader column header semantics

Phase 3 — Dashboard
- New <Dashboard> composable component (src/components/Dashboard/Dashboard.tsx)
  - Slots: Dashboard.Header / Title / Subtitle / Actions / Grid / Widget
  - Responsive 1 → 6 → 12 column grid (mobile / tablet / desktop)
  - Density + brand-spacing aware
- Exported from src/index.ts
- Removed stale .bak / .backup / .broken story files

Phase 4 — Verification
- New tests/visual/a11y.spec.ts with axe-core (WCAG 2.1 A+AA)
- 14 core stories scanned, zero critical/serious violations
- @axe-core/playwright added as devDependency

Validation
- pnpm typecheck PASS
- pnpm lint PASS
- pnpm test (vitest) 113/113 PASS
- pnpm build (tsup ESM+CJS+DTS) PASS
- pnpm test:visual: 20/20 baseline regressions PASS (within 5% tolerance)
- pnpm test:visual: 14/14 a11y PASS
Copilot AI review requested due to automatic review settings May 19, 2026 17:16
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 19, 2026

Deploying ui with  Cloudflare Pages  Cloudflare Pages

Latest commit: b95d33c
Status: ✅  Deploy successful!
Preview URL: https://c42975f2.ui-6d0.pages.dev
Branch Preview URL: https://feat-ui-polish-phase1-4.ui-6d0.pages.dev

View logs

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR is a broad UI “polish sprint” that extends the design-token foundation (spacing/density/motion/focus-ring/elevation), standardizes :focus-visible usage across components, adds an axe-core Playwright accessibility suite, and introduces a new composable Dashboard component exported from the library.

Changes:

  • Added brand token groups (spacing/density/motion/focus ring) + elevation ramp and exposed them via CSS variables and Tailwind preset utilities/safelist.
  • Standardized focus styling across many components by moving rings to focus-visible:* utilities and made a few semantic color/accessibility tweaks (e.g., warning status alert, th scope="col").
  • Added axe-core Playwright tests and a new Dashboard slot-based component.

Reviewed changes

Copilot reviewed 61 out of 62 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/visual/components.spec.ts Minor formatting change to a story navigation call.
tests/visual/a11y.spec.ts New Playwright/axe accessibility test suite targeting core stories.
src/tailwind-preset.ts Added new token utilities to theme + extended Tailwind safelist.
src/styles/base.css Added elevation/spacing/motion/focus-ring CSS variables + density selector + focus-visible outline tokenization.
src/index.ts Exported new Dashboard component from the package entry point.
src/components/WebsiteInput/WebsiteInput.tsx Switched ring styling to focus-visible:*.
src/components/Toast/Toast.tsx Switched ring styling to focus-visible:*.
src/components/ThemeProvider/ThemeToggle.tsx Switched ring styling to focus-visible:*.
src/components/Textarea/Textarea.tsx Switched ring styling (incl. error state) to focus-visible:*.
src/components/Table/Table.tsx Added scope="col" to table header cells for improved semantics.
src/components/StepIndicator/StepIndicator.tsx Switched ring styling to focus-visible:*.
src/components/Sidebar/Sidebar.tsx Switched ring styling to focus-visible:*.
src/components/SetupServiceModal/SetupServiceModal.tsx Switched ring styling to focus-visible:*.
src/components/ServiceShippingSettings/ServiceShippingSettings.tsx Switched ring styling to focus-visible:*.
src/components/ServiceGeneralSettings/ServiceGeneralSettings.tsx Switched ring styling to focus-visible:*.
src/components/ServiceBadge/ServiceBadge.tsx Switched ring styling to focus-visible:*.
src/components/Select/Select.tsx Switched ring styling (incl. error state + searchable input) to focus-visible:*.
src/components/SchedulePicker/SchedulePicker.tsx Switched ring styling to focus-visible:*.
src/components/RejectionModal/RejectionModal.tsx Switched ring styling to focus-visible:*.
src/components/RecurringServiceCard/RecurringServiceCard.tsx Switched ring styling to focus-visible:*.
src/components/ProviderSelector/ProviderSelector.tsx Switched ring styling to focus-visible:*.
src/components/ProviderSearchFilters/ProviderSearchFilters.tsx Switched ring styling to focus-visible:*.
src/components/ProviderOverview/ProviderOverview.tsx Updated skeleton colors to semantic bg-muted.
src/components/ProviderDetailHeader/ProviderDetailHeader.tsx Switched ring styling to focus-visible:*.
src/components/PhoneInput/PhoneInput.tsx Switched ring styling to focus-visible:*.
src/components/PaymentMethod/PaymentMethod.tsx Switched ring styling to focus-visible:*.
src/components/PatientHeader/PatientHeader.tsx Switched ring styling to focus-visible:*.
src/components/OrderList/OrderList.tsx Switched ring styling to focus-visible:*.
src/components/OrderConfirmationWizard/OrderConfirmationWizard.tsx Switched ring styling to focus-visible:*.
src/components/Messaging/MessageThread.tsx Switched ring styling to focus-visible:*.
src/components/Messaging/MessageList.tsx Switched ring styling to focus-visible:*.
src/components/Messaging/MessageComposer.tsx Switched ring styling to focus-visible:*.
src/components/Messaging/MessageBubble.tsx Switched ring styling to focus-visible:*.
src/components/Messaging/ConversationHeader.tsx Switched ring styling to focus-visible:*.
src/components/Messaging/AttachmentPicker.tsx Switched ring styling to focus-visible:*.
src/components/LanguageSelector/LanguageSelector.tsx Switched ring styling to focus-visible:*.
src/components/InviteUserModal/InviteUserModal.tsx Switched ring styling to focus-visible:*.
src/components/Input/Input.tsx Switched ring styling (incl. error state) to focus-visible:*.
src/components/HRISProviderSelector/HRISProviderSelector.tsx Converted pending-sync alert to semantic warning tokens + added role="status" + focus-visible ring updates.
src/components/EmployerServiceModal/EmployerServiceModal.tsx Switched ring styling to focus-visible:*.
src/components/DateRangePicker/DateRangePicker.tsx Switched ring styling to focus-visible:*.
src/components/DateInput/DateInput.tsx Switched ring styling (incl. error state) to focus-visible:*.
src/components/Dashboard/index.ts New Dashboard barrel export.
src/components/Dashboard/Dashboard.tsx New composable Dashboard component with slot API and responsive grid/widget spans.
src/components/Dashboard/Dashboard.stories.tsx.broken Removed broken backup story file.
src/components/Dashboard/Dashboard.stories.tsx.bak Removed backup story file.
src/components/Dashboard/Dashboard.stories.tsx.backup Removed backup story file.
src/components/CountryCodeDropdown/CountryCodeDropdown.tsx Switched ring styling to focus-visible:*.
src/components/CountBadge/CountBadge.tsx Switched ring styling to focus-visible:*.
src/components/BookingDialog/BookingDialog.tsx Switched ring styling to focus-visible:*.
src/components/Badge/Badge.tsx Switched badge palettes from literal colors to semantic success/warning/destructive tokens.
src/components/AuthDialog/AuthDialog.tsx Switched ring styling to focus-visible:*.
src/components/AppHeader/AppHeader.tsx Switched ring styling to focus-visible:*.
src/components/AI/MCPToolCall.tsx Switched ring styling to focus-visible:*.
src/components/AI/AIChatModal.tsx Switched ring styling to focus-visible:*.
src/components/Address/Address.tsx Switched ring styling to focus-visible:*.
src/brands/types.ts Extended brand token types + defaults; updated CSS/Tailwind theme generation to emit new token variables.
src/brands/bluehive.ts Populated BlueHive brand config with new token groups (spacing/density/motion/focus ring/elevation).
src/brands/bluehive.css Added new token CSS variables and focus-ring styling to the BlueHive standalone CSS.
pnpm-lock.yaml Added @axe-core/playwright (and updated axe-core snapshot).
package.json Added @axe-core/playwright dev dependency.
.storybook/preview.tsx Applied data-density globally and injected brand token CSS variables (spacing/motion/focus/elevation) into Storybook.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/visual/a11y.spec.ts Outdated
Comment thread src/tailwind-preset.ts
Comment thread src/brands/types.ts
Comment thread src/brands/types.ts
- a11y.spec: throw on Storybook error/no-preview state (mirror components.spec); fix
  6 wrong story IDs (Badge/Card/Progress/Avatar/Tabs/Toast/Alert) so failures surface
- tailwind-preset: safelist Dashboard responsive grid (col-span-full, sm:col-span-*,
  lg:col-span-*, lg:grid-cols-12, md:p-brand-xl, sm:gap-brand-lg) and focus-visible
  ring utilities (ring-2, ring-offset-2, ring-primary, ring-destructive, ring-success,
  ring-success/20, ring-destructive/20, border-transparent)
- BrandDensity.default now drives the initial --mieweb-density-scale; added
  [data-density='comfortable'] reset selector so brands defaulting to compact can
  be flipped back at runtime
- BrandFocusRing.color now emits --mieweb-focus-ring-color mapped to a semantic
  token (ring/primary/destructive); consumed by base.css, bluehive.css, and the
  Storybook preview :focus-visible outline

Validation: tsc, eslint, prettier, vitest (113/113), build-storybook, playwright
(20 visual + 14 a11y = 34/34) all green.
@wreiske
Copy link
Copy Markdown
Member Author

wreiske commented May 19, 2026

Addressed all 4 Copilot review comments in b95d33c:

  1. a11y.spec error swallowgotoStory now mirrors components.spec.ts: detects Storybook sb-show-errordisplay / sb-nopreview states and throws Story 'X' failed to render. This immediately surfaced 6 wrong story IDs in the a11y suite (Badge/Card/Progress/Avatar/Tabs/Toast/Alert) — also fixed.
  2. Tailwind v3 safelist — added Dashboard responsive grid utilities (col-span-full, sm:col-span-*, lg:col-span-*, lg:grid-cols-12, md:p-brand-xl, sm:gap-brand-lg) and focus-visible ring utilities (focus-visible:ring-2, ring-offset-2, ring-primary, ring-destructive, ring-success, ring-success/20, ring-destructive/20, border-transparent) to miewebUISafelist.
  3. BrandDensity.default was a no-op — the brand generator now emits --mieweb-density-scale: <compactScale> when default === 'compact', and a new [data-density='comfortable'] selector resets it to 1 so brands defaulting to compact can be flipped back at runtime. Storybook preview mirrors the same logic.
  4. BrandFocusRing.color was a no-op — the generator now emits --mieweb-focus-ring-color: var(--mieweb-${color}) (defaults to ring, can map to primary/destructive/etc.). :focus-visible outlines in base.css, bluehive.css, and the Storybook preview now consume this var with a fallback to --mieweb-ring.

Validation: typecheck, eslint, prettier, vitest (113/113), build-storybook, and playwright (20 visual + 14 a11y = 34/34) all green.

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.

2 participants