Skip to content

feat(inputs): add statusVariant to the bordered input family (#4187)#4200

Draft
cixzhang wants to merge 1 commit into
mainfrom
feat/input-statusvariant-convention
Draft

feat(inputs): add statusVariant to the bordered input family (#4187)#4200
cixzhang wants to merge 1 commit into
mainfrom
feat/input-statusvariant-convention

Conversation

@cixzhang

Copy link
Copy Markdown
Contributor

Summary

Adds a single optional prop — statusVariant?: 'attached' | 'detached' (default 'attached') — to the bordered input family, forwarding it verbatim to the Field each input already renders. Field/FieldStatus have implemented both placements for a while; the bordered inputs just never exposed the knob, so they were locked to the implicit attached default. This threads it through.

  • attached (default) — the status message sits directly below the input with an overlapping treatment. This is exactly today's behavior for these inputs.
  • detached — the status message floats below as a separate element with spacing.

Closes #4187.

Why

The placement behavior already exists at the primitive layer (Field.statusVariantFieldStatus), but the bordered inputs couldn't reach it. Consumers who wanted a floated (detached) status under a bordered input had no supported way to get one. This is pure plumbing of an existing capability — no new infrastructure.

What changed

Gets the prop — bordered family (12):
TextInput, TextArea, NumberInput, DateInput, DateRangeInput, TimeInput, Selector, MultiSelector, Typeahead, Tokenizer, FileInput, and PowerSearch (which forwards to the bordered input it renders internally).

Each one:

  • Adds statusVariant?: FieldStatusVariant to its props interface (JSDoc mirroring Field's).
  • Destructures it with a default of 'attached'.
  • Forwards a single line, statusVariant={statusVariant}, to the Field it renders.

Deliberately NOT touched — borderless / grouped controls (7):
DateTimeInput, CheckboxList, RadioList, Slider, InputGroup, Switch, CheckboxInput.

These keep their hardcoded detached and get no public prop. attached is an overlap treatment designed for a full-width bordered input box; borderless and grouped controls have no such box to overlap, so attached renders wrong on them. Exposing a prop whose only non-default value is broken on those controls would be a footgun, so the prop is offered only where both values are valid. Their existing detached literal is left exactly as-is — zero source change.

Non-breaking

The default 'attached' matches what these inputs already produced (they rendered Field with no statusVariant, and Field's own default is 'attached'). Existing code compiles and renders identically; opting into detached is the only behavior change, and it's explicit.

Type reuse

The prop reuses the existing FieldStatusVariant type (imported through Field's barrel). The union is not redefined per component — one type, extensible in one place, no drift. No changes to Field or FieldStatus.

Testing

  • Added a colocated test to each of the 12 components asserting the value is forwarded: default renders the status element with data-variant="attached"; statusVariant="detached" renders it with data-variant="detached".
  • Added a StatusVariantComparison Storybook story to each component showing attached vs detached side by side.
  • Ran the touched test files, plus pnpm lint (0 errors on changed files), @astryxdesign/core typecheck + typecheck:docs, storybook typecheck, pnpm build, sync-exports --check, verify-exports, generate-token-docs --check, and changeset validation — all green.
  • Updated each component's .doc.mjs prop table (all locale/dense variants present) with the new prop.

@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
astryx Ready Ready Preview, Comment Jul 22, 2026 12:38pm

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 22, 2026
github-actions Bot added a commit that referenced this pull request Jul 22, 2026
@cixzhang

Copy link
Copy Markdown
Contributor Author

Self-review — against the Component Build Protocol (Quick Self-Review for updates)

This is a plumbing/convention change (exposing an existing Field/FieldStatus capability), so the lighter refactor checklist applies.

  1. Icons from registry — n/a; no icons touched.
  2. themeProps present — n/a; no new interactive elements. The forwarded prop reaches FieldStatus, which already emits data-variant via themeProps.
  3. No wrapper divs — n/a; no new DOM. The prop is a single forwarded attribute on the existing <Field>.
  4. No CSS shorthands — n/a; no styles changed.
  5. ARIA still correct — no primitive/hook swaps. detached vs attached only changes whether children + status share a wrapper vs are siblings (existing Field behavior); status roles (alert/status) are unchanged. Verified by the touched components' tests.
  6. Tests pass locally — ran all 12 touched test files (green), then the full suite: 7373 passed, 0 failures. Also: pnpm lint (0 errors on changed files), core typecheck + typecheck:docs, storybook typecheck, pnpm build, sync-exports --check, verify-exports, generate-token-docs --check, changeset validation — all green.
  7. Stories updated — added a StatusVariantComparison story to each of the 12 components (attached vs detached side by side). No props removed.

Spec-compliance notes

  • Scope exact: 12 bordered inputs get the prop; the 7 borderless/grouped controls (DateTimeInput, CheckboxList, RadioList, Slider, InputGroup, Switch, CheckboxInput) are untouched — their hardcoded detached is preserved, and there is zero source change to them (or to Field/FieldStatus). Confirmed by the diff.
  • Type reuse: every component imports FieldStatusVariant through Field's barrel; the 'attached' | 'detached' union is not redefined anywhere.
  • Non-breaking: default is 'attached', matching the prior implicit behavior.
  • PowerSearch: forwards statusVariant to the bordered input it renders internally, which forwards to Field — consistent with the "input owns its Field" model.
  • Family alignment: the prop name and JSDoc mirror the existing Field.statusVariant.

@github-actions

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

Modified Components

DateInput · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 500 -
Complexity N/A Very High (80) -
DateRangeInput · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 485 -
Complexity N/A Very High (56) -
FileInput · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 632 -
Complexity N/A Very High (95) -
MultiSelector · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 1278 -
Complexity N/A Very High (139) -
NumberInput · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 500 -
Complexity N/A Very High (76) -
PowerSearch · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 3665 -
Complexity N/A Very High (335) -
Selector · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 1273 -
Complexity N/A Very High (101) -
TextArea · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 350 -
Complexity N/A Very High (45) -
TextInput · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 353 -
Complexity N/A Very High (48) -
TimeInput · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 493 -
Complexity N/A Very High (76) -
Tokenizer · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 656 -
Complexity N/A Very High (98) -
Typeahead · View in Storybook
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 1096 -
Complexity N/A Very High (152) -

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@astryxdesign/core N/A 4.7KB 0B

Accessibility Audit

Status: 6 accessibility violation(s) found — 2 critical, 4 serious.

DateRangeInput - 1 issue(s)
  • 🔴 critical: Ensure an element's role supports its ARIA attributes
    • Rule: aria-allowed-attr · Affects 1/17 stories · Learn more
    • WCAG: 4.1.2 (Level A)
FileInput - 3 issue(s)
  • 🟠 serious: Ensure interactive controls are not nested as they are not always announced by screen readers or can cause focus problems for assistive technologies
    • Rule: nested-interactive · Affects 13/15 stories · Learn more
    • WCAG: 4.1.2 (Level A)
  • 🔴 critical: Ensure an element's role supports its ARIA attributes
    • Rule: aria-allowed-attr · Affects 1/15 stories · Learn more
    • WCAG: 4.1.2 (Level A)
  • 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
    • Rule: color-contrast · Affects 2/15 stories · Learn more
    • WCAG: 1.4.3 (Level AA)
PowerSearch - 1 issue(s)
  • 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
    • Rule: color-contrast · Affects 2/25 stories · Learn more
    • WCAG: 1.4.3 (Level AA)
Tokenizer - 1 issue(s)
  • 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
    • Rule: color-contrast · Affects 2/21 stories · Learn more
    • WCAG: 1.4.3 (Level AA)

Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add statusVariant (attached/detached) as a shared convention across all inputs

1 participant