Commit Graph

3 Commits

Author SHA1 Message Date
Guillem Arias Fauste
f2782901d3 fix(design-system): DS::Link a11y — distinguishable default, icon-only label, external-link hardening (#1844)
* fix(design-system): DS::Link a11y — distinguishable default,
icon-only label, external-link hardening

Closes #1739. DS::Link extends Buttonish, so the styled variants
(`:primary`, `:secondary`, `:icon`, `:ghost`, etc.) inherit the
Buttonish styling pipeline. The `default` variant is the bare
inline link, which had multiple a11y gaps:

1. **WCAG 1.4.1 — color is not the only difference.** The default
   variant had `container_classes: ""`, so a link rendered as plain
   text-color text with no underline, no weight change, nothing.
   Color-only differentiation fails WCAG 1.4.1 for low-vision and
   colorblind users. Now: `text-link underline underline-offset-2
   hover:no-underline` — underlined at rest, underline removed on
   hover for a polish hint, plus the `text-link` token (blue-600
   light / blue-500 dark) for color.

2. **Focus ring.** `<a>` doesn't pick up the `button` focus rule
   from base.css (#1738). Add `focus-visible:outline-2
   outline-offset-2 outline-gray-900 theme-dark:outline-white`
   directly on the default variant. The Buttonish-derived variants
   render as buttons visually but as `<a>` in markup — out of scope
   here; covered by their own callsites styling.

3. **Icon-only accessible name.** Mirror the DS::Button fix from
   #1738: derive a humanized `aria-label` from the icon key when
   the caller doesn't provide one, so AT users hear "More
   horizontal" instead of just the URL.

4. **External-link hardening.** `target="_blank"` without
   `rel="noopener"` exposes `window.opener` to the new tab
   (reverse-tabnabbing). Always set `noopener noreferrer` when the
   target is `_blank`. Authors can override by passing `rel:`
   explicitly.

5. **sr-only "(opens in new tab)" hint.** Append an `sr-only` span
   after the link text when `target="_blank"` so AT users hear the
   navigation behavior. Visual indication (e.g. an external-link
   icon) stays at the caller's discretion.

Locale key: `ds.link.opens_in_new_tab` (en only — other locales in
a separate translation pass per repo norm).

API unchanged. No existing callsites use `target="_blank"` or
icon-only links, so no migration needed.

* fix(review): fold new-tab cue into icon-only aria-label

When an icon-only DS::Link also targets `_blank`, the generated
`aria-label` was overriding the descendant accessible name, masking
the sr-only "(opens in new tab)" span. Include the cue directly in
the generated label so AT users hear the warning. Also switch
`capitalize` to `humanize` so multi-word icon keys like
`external-link` read as "External link" rather than "External link"
already worked but `humanize` is the more idiomatic Rails choice and
keeps us aligned with the suggested patch.

Flagged by Codex P2 + CodeRabbit on PR #1844.

* fix(review): swap raw outline palette to alpha-ring tokens

Codex P1 follow-up after the ready-for-review transition: the default
\`DS::Link\` focus ring used raw \`outline-gray-900\` +
\`theme-dark:focus-visible:outline-white\`, which violates the DS-hygiene
rule that bans raw Tailwind palette utilities in component styling.

Swap to the established alpha-ring pattern already used by DS::Toggle
(#1843), DS::Tooltip (#1845), provider_card, and form-field —
\`focus-visible:ring-2 focus-visible:ring-alpha-black-300\` +
\`theme-dark:focus-visible:ring-alpha-white-300\`. Same visual contract
(WCAG 1.4.11), theme tokens centralized.
2026-05-20 18:16:20 +02:00
Guillem Arias Fauste
0fe1e06645 refactor(design-system): migrate fg-* utilities to text-* and remove namespace (#1626)
* refactor(design-system): migrate fg-* utilities to text-* and remove namespace

The design system carried two parallel namespaces for foreground colors:
text-* (canonical, ~2,000 uses) and fg-* (32 uses). Most fg-* tokens
were 1:1 duplicates of a text-* counterpart. fg-gray was nearly
identical to text-secondary, with a one-step shade difference in dark
mode.

This PR migrates all 32 usages to their text-* equivalents and removes
the fg-* block from the design tokens. Closes #1606.

Mapping:
- fg-inverse  -> text-inverse  (20 usages, identical light/dark values)
- fg-gray     -> text-secondary (7 usages; light values match, dark is
                                 one step lighter: gray-300 vs gray-400)
- fg-primary  -> text-primary  (3 usages, identical values)
- fg-subdued  -> text-subdued  (2 usages, identical values)

The four other fg-* tokens (fg-contrast, fg-primary-variant,
fg-secondary, fg-secondary-variant) had zero usages despite being
defined; they are removed without replacement.

JSON / build:
- design/tokens/sure.tokens.json: $version 1.0.0 -> 2.0.0 (breaking
  schema change per the policy added in #1620). 8 fg-* token
  definitions removed.
- button-bg-ghost-hover's dark value still references "fg-inverse"
  internally; rewritten to "bg-gray-800 text-inverse" so the cleanup
  doesn't break that utility.
- _generated.css regenerated. 42 utility blocks now (was 50).

Lookbook tokens preview:
- The Text & foregrounds section dropped its split between text-*
  (canonical) and fg-* (legacy). Now a single section listing the
  five text-* utilities. The "(legacy)" framing is gone since there's
  no legacy left.

README:
- design/tokens/README.md's button-bg-ghost-hover edge-case example
  updated to reflect the new "bg-gray-800 text-inverse" dark value.

Visual review needed in dark mode:
- Anywhere icons use the application_helper#icon helper with
  color: "default" (most icons in the app). The default class moved
  from fg-gray (gray-400 dark) to text-secondary (gray-300 dark), so
  default-color icons render slightly lighter in dark mode.
- DS::Buttonish icons in secondary buttons (same shade shift).
- DS::Link icons (same).
- Time series chart axes (same).
- All tooltips, account add flow, settings hostings buttons,
  invitations, AI consent, family export, danger-zone buttons --
  these used fg-inverse, which is identical to text-inverse, so no
  visual change expected.

* fix(design-system): use inverse pair on tooltips for readable dark mode

* fix(lookbook): use semantic tokens in menu preview header text

* fix(lookbook): set text-primary on layout body so previews inherit theme

* fix(design-system): keep shadows dark-toned in dark mode

Inverting shadows to white|8% on dark surfaces produces a halo
effect rather than an elevation cue, and stacks redundantly with
the alpha-white 1px ring already in shadow-border-*.

Switch dark-mode shadows to black at progressively higher alpha
(25%/30%/35%/40%/50% for xs..xl) so they read as actual cast
shadows on near-black surfaces. Surface-tint differences and the
existing alpha-white border ring continue to handle elevation
hierarchy and edge definition.

Approach matches Material 3, Apple HIG, IBM Carbon, Refactoring UI,
and the dark-mode shadows used in Linear/Vercel/Stripe.

* fix(design-system): set text-primary on DS::Dialog element

Browser UA stylesheets apply color: black directly to <dialog>,
which overrides ancestor inheritance even when a body or html
ancestor sets a theme-aware color. Unstyled child content then
renders black regardless of theme.

Setting text-primary on the dialog element itself defeats the UA
override and lets descendants inherit the semantic token.

* fix(lookbook): use shadow css vars in effects preview so dark theme renders

* Revert "fix(design-system): keep shadows dark-toned in dark mode"

This reverts commit 3e9d76ed0b.

* fix(design-system): use opacity-70 instead of text-inverse/70 in value tooltip

The custom @utility text-inverse expands to @apply text-white and
isn't modifier-aware, so text-inverse/70 produced no CSS at all and
the muted labels fell through to inherited color (invisible on the
white pill in dark mode).

Replace with text-inverse + opacity-70. Same visual effect, works
with the existing utility definition.
2026-05-04 00:50:52 +02:00
Zach Gollwitzer
ab6fdbbb68 Component namespacing (#2463)
* [claudesquad] update from 'component-namespacing' on 18 Jul 25 07:23 EDT

* [claudesquad] update from 'component-namespacing' on 18 Jul 25 07:30 EDT

* Update stimulus controller references to use namespace

* Fix remaining tests
2025-07-18 08:30:00 -04:00