Skip to content

feat: add shared glow states for selection, hover, focus and overlays (2.7.0) - #102

Merged
JJLiebig merged 2 commits into
mainfrom
feat/consistent-glow
Oct 20, 2026
Merged

JJLiebig merged 2 commits into
mainfrom
feat/consistent-glow

Conversation

@JJLiebig

@JJLiebig JJLiebig commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

What

One shared way to draw selection, hover, focus, button and overlay glow, driven by a single tone variable, plus one keyboard-focus look across every shared control. Selected items glow; they never lift. Release version 2.7.0. The manifest and install examples are bumped, so a release can be cut from the merge (approved to release right after merge).

Need Use
Choosable card, row or tile (hover, focus, selected) nextide-glow on the interactive element, or selectable on Card / Surface. Selection follows data-selected, aria-pressed, aria-selected or aria-checked.
Selection decided in code nextide-glow-selected
Status (recommended, winning, playing) nextide-tone-border: toned border only, no glow
Keyboard focus Built into every shared control. For stylesheets, use nextide-focus-ring.
Button Built in: shared focus, hover glow on primary, destructive halo in its own tone
Overlay / hero light shadow-glow-overlay (halo around a floating mark), nextide-glow-wash (radial wash, centre via --nextide-glow-origin)
Colour --nextide-glow-tone on the element or any ancestor (Signal Turquoise by default), e.g. an A/B option's own colour

Selected look (owner decision: toned-down backlight)

  • Default: border at 70% tone, a 1px ring at 40%, outer light 0 0 30px -2px at 26%, and a soft backlight (0 0 64px 14px at 14%).
  • Spill: the backlight spills onto neighbouring items. The selected item sits on a higher stacking layer (--nextide-glow-layer: 1, effective for grid/flex items or positioned elements) but never moves.
  • Approximation: the backlight is an outer shadow rather than a blurred ::before. It never paints under the card itself and needs no wrapper element. It approximates the Daedalus reference (radial at opacity 0.22, blur 30px, inset -40px -36px).
  • Tokens: --nextide-glow-border, -ring, -halo, -halo-size, -backlight, -backlight-size, -layer.
  • Halo preset: data-nextide-glow="halo" restores the earlier tighter halo.
  • Inner look: removed to keep the API small.
  • Playground: the "Glow states" example switches between backlight and halo.

One focus look (owner decision)

  • The look: every shared control uses the border plus a thin ring in the focus colour (--nextide-focus-ring-width, 0.5px), with the tone halo behind it.
  • Class form: focus-visible:border-ring focus-visible:ring-(length:--nextide-focus-ring-width) focus-visible:ring-ring focus-visible:shadow-glow-focus focus-visible:outline-hidden.
  • Forced colours: the transparent outline shows only in forced-colours mode, where shadows disappear.
  • Replaced: 2px rings, translucent rings, outline-only and brightness-only focus, and destructive-tinted (20%) rings. This covers 24 components and blocks, including the toast, charts, schedule matrix, navigation, creator transfer, tabs, slider and scroll area.
  • Fields: Input, Select and Autocomplete draw the same ring inside their border and keep pointer focus quiet.
  • Fixed on the way: outline buttons in dark mode now take the focus border; their dark border used to override it.

Library consistency

Stream selector, creator scope panel, run monitor and date range picker used their own selection shadows. They now use the shared selected glow. cn treats the glow shadows as shadows, so className overrides still replace them.

Accessibility

  • Focus outranks selection in the cascade, so the ring always shows on a selected item.
  • Forced-colours mode: a Highlight outline marks selected items, and the focus outline is visible.
  • Reduced motion: transitions use the shared motion tokens, which collapse to 1ms. Nothing uses transforms.

Contract

DESIGN.md covers both rules: glow marks state, selection never lifts, status uses a toned border only, and there is one focus look. docs/component-map.md covers usage, the tokens and the canonical focus classes.

Validation (on 29ab548)

  • just pre-commit passed: Prettier, Oxlint --deny-warnings, the supply-chain watchlist and all four TypeScript projects. It ran in the commit hook.
  • just test (packed-consumer package acceptance) passed, 7/7. It includes the check that the glow shadows merge correctly.
  • npm run qualify (build plus native E2E, desktop 1440 and mobile 390, replay only) passed, 57/57.
  • Journey test in tests/controls.e2e.ts: a chosen card glows while its box and transforms stay unchanged, and keyboard focus on a selected card shows the focus ring. Mutation check: a 2px lift makes it fail.
  • Viewports: the Glow states example was captured at 320, 390, 768 and 1440 for backlight and halo, selected and focused, with no horizontal page overflow. Field and button keyboard focus were checked at 390.

Not checked: the light theme visually (its focus ring is the existing Focus Teal) and browsers without color-mix support.

🤖 Generated with Claude Code

… (2.7.0)

Products drew selection, hover and focus glows with ad-hoc shadows in
different sizes and colours, and some selected cards lifted. The package
now owns one set of glow states, driven by one tone (--nextide-glow-tone).

- nextide-glow: hover, keyboard focus and selection for a choosable
  card, row or tile. Selection follows data-selected, aria-pressed,
  aria-selected or aria-checked. Focus keeps its opaque ring on top of
  a selected glow. Nothing translates or scales.
- Card and Surface take selectable to opt in.
- nextide-glow-selected for selection decided in code, and
  nextide-tone-border for status such as recommended or playing.
- Button: focus halo on every variant, hover glow on primary,
  destructive glows in its own tone.
- Overlay light: shadow-glow-overlay and nextide-glow-wash.
- The selected look is built from tokens (border, ring, halo, backlight,
  inset, wash, layer), with data-nextide-glow="backlight" and "inner"
  presets, so the default can change without new classes.
- Stream selector, creator scope panel, run monitor and date range
  picker use the shared selected glow instead of their own shadows.
- cn treats the glow shadows as shadows, checked against the shipped
  stylesheet in package acceptance.

Release version: 2.7.0.
Owner decisions on the shared glow states:

- The selected look defaults to a toned-down backlight: border at 70%
  tone, a 1px ring at 40%, outer light 0 0 30px -2px at 26%, and a soft
  backlight that spills onto neighbours from a raised stacking layer.
  The item never moves. The earlier halo stays as
  data-nextide-glow="halo"; the inner look and its inset and wash
  tokens are removed.
- Every shared control uses one keyboard-focus look: border and thin
  ring in the focus colour plus the tone halo, with a transparent
  outline that shows in forced-colours mode. This replaces 2px,
  translucent, outline-only, brightness-only and destructive-tinted
  focus rings across 24 components and blocks. Fields draw the same
  ring inside their border. Stylesheets get nextide-focus-ring.
- Outline buttons in dark mode now take the focus border, which their
  dark border previously overrode.
@JJLiebig
JJLiebig merged commit 8084f6a into main Oct 20, 2026
2 checks passed
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