Skip to content

feat(core): add useTableRowStatus plugin#3757

Merged
humbertovirtudes merged 6 commits into
facebook:mainfrom
humbertovirtudes:navi/feat/table-row-status
Jul 23, 2026
Merged

feat(core): add useTableRowStatus plugin#3757
humbertovirtudes merged 6 commits into
facebook:mainfrom
humbertovirtudes:navi/feat/table-row-status

Conversation

@humbertovirtudes

Copy link
Copy Markdown
Contributor

What

Adds useTableRowStatus — a plugin that prepends a narrow column rendering a full-height colored bar on each row's leading edge: a compact per-row status signal (error, warning, unread, etc.) without a dedicated status column.

const rowStatus = useTableRowStatus({
  getStatus: row =>
    row.state === 'error'
      ? {color: 'var(--color-icon-red)', label: 'Error'}
      : null,
});
<Table data={data} columns={columns} idKey="id" plugins={{rowStatus}} />;

Design

  • getStatus(item) maps a row to {color, label?} — any CSS color (token or raw) plus an optional accessible label — or null for no indicator.
  • Prepends a fixed 8px, non-resizable __rowStatus column with an empty header; transformBodyCell zeroes the cell padding so a 4px absolutely-positioned bar spans the row's full height on the leading edge.
  • The bar exposes role="img" + aria-label/title when a label is given, so status is announced and hoverable.

Includes

  • Storybook (Core/TableRowStatus): Default (theme-token colors, mixed statuses incl. "no bar") and RawColors (hex).
  • Docsite: useTableRowStatus hook page + a "Status Bars" example block.
  • Table Lab: added as a toggle (colors by member status).
  • Unit tests (4): narrow status column + empty header, labeled bars for statused rows, no bar for null, and color applied to the bar.

Validation

  • Build ✅, lint ✅, sandbox typecheck ✅
  • 144 tests pass (rowStatus + full Table suite, no regressions); changeset included.

@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
astryx Error Error Jul 23, 2026 8:58am

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 10, 2026
Comment thread apps/sandbox/src/app/(sandbox)/pages/table-lab/page.tsx
Comment thread packages/core/src/Table/plugins/rowStatus/useTableRowStatus.tsx
@cixzhang cixzhang added area:core needs:code-review High-risk change (new package/component/API) — needs human code review before merge and removed area:core labels Jul 11, 2026
@github-actions github-actions Bot added needs:design-review Affects visuals — Design should review and removed needs:code-review High-risk change (new package/component/API) — needs human code review before merge labels Jul 12, 2026
@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

Modified Components

Banner
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 322 -
Complexity N/A High (22) -
Chat
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 5240 -
Complexity N/A Very High (425) -
CodeBlock
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 1560 -
Complexity N/A Very High (83) -
DateTimeInput
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 784 -
Complexity N/A Very High (141) -
FileInput
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 631 -
Complexity N/A Very High (95) -
MobileNav
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 364 -
Complexity N/A Very High (37) -
NumberInput
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 495 -
Complexity N/A Very High (76) -
PowerSearch
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 3661 -
Complexity N/A Very High (335) -
SideNav
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 1848 -
Complexity N/A Very High (179) -
Table
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 1918 -
Complexity N/A Very High (101) -
authoring
Metric Before After Delta
Bundle Size (ESM) N/A N/A N/A
Lines of Code N/A 124 -
Complexity N/A Low (1) -

Bundle Size Summary

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

Accessibility Audit

Status: 7 accessibility violation(s) found — 1 critical, 6 serious.

Banner - 1 issue(s)
  • 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
    • Rule: color-contrast · Affects 1/15 stories · Learn more
    • WCAG: 1.4.3 (Level AA)
Chat - 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/8 stories · Learn more
    • WCAG: 1.4.3 (Level AA)
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 12/14 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/14 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/14 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/24 stories · Learn more
    • WCAG: 1.4.3 (Level AA)
Table - 1 issue(s)
  • 🟠 serious: Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds
    • Rule: color-contrast · Affects 4/24 stories · Learn more
    • WCAG: 1.4.3 (Level AA)

Generated by PR Enrichment workflow | View full report

@humbertovirtudes

Copy link
Copy Markdown
Contributor Author

@ernestt can I have your design eyes here pls

@ernestt ernestt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks again for continuing all the plugins! @humbertovirtudes Curious why we are diverging from the original version that shows either a status dot or icon? My 2 worries with this design are

  1. Status is conveyed through color alone
  2. The shape of this is similar to out tab selection state. Might be interpreted as selected row?
CleanShot 2026-07-17 at 09 29 52

@humbertovirtudes

Copy link
Copy Markdown
Contributor Author

Thanks again for continuing all the plugins! @humbertovirtudes Curious why we are diverging from the original version that shows either a status dot or icon? My 2 worries with this design are

  1. Status is conveyed through color alone
  2. The shape of this is similar to out tab selection state. Might be interpreted as selected row?
CleanShot 2026-07-17 at 09 29 52

the vertical color feels less invasive / space consuming that a status dot. However if status dot is preferred for consistency, Im down

Prepends a narrow column that renders a full-height colored bar on each
row's leading edge — a compact per-row status signal (error, warning,
unread, etc.) without a dedicated status column.

- getStatus(item) maps a row to {color, label?} (any CSS color/token +
  optional accessible label), or null for no indicator.
- Storybook stories (token colors + raw colors), docsite hook doc +
  example block, unit tests, and a Table Lab toggle.
Address review feedback (cixzhang) and self-review findings:

- Fix invalid token in the flagship @example: 'var(--color-icon-error)' does
  not exist. The API now accepts semantic color NAMES first-class.
- Semantic colors (cixzhang): color accepts 'success'|'error'|'warning'|
  'accent'|'red'|'orange'|'green'|'yellow'|'blue'|'gray', mapped to theme
  tokens; raw CSS color strings still pass through as an escape hatch.
- Icon signifier (cixzhang): optional `icon` renders an Icon as the status
  signifier so status is conveyed by shape + color, not color alone — more
  accessible when multiple statuses coexist. Column widens to fit the icon.
- a11y: role="img" + aria-label only when a label is provided; documented that
  omitting label yields a color-only (inaccessible) indicator.
- Document that getStatus should be memoized (useCallback) for a stable
  plugin identity; updated doc.mjs + config JSDoc.
- Tests (8, all green): semantic-color→token mapping, raw-CSS escape hatch,
  icon-mode renders an svg + keeps the accessible name, label-omitted → no
  role=img (color-only bar still renders), and empty-data (header, no bars).
- Updated the Storybook Default story and Table Lab example to the idiomatic
  semantic-color + icon form.

Tags: ai-generated

This diff was generated with AI assistance (Navi). The implementation and
tests were produced by an AI agent; the human author reviewed and approved
the changes.
Switch the default indicator from a full-height leading-edge bar to a
centered status dot, per review feedback:
- the bar's shape read as the tab selection state
- a dot matches the original status-dot / icon pattern

Icon mode is unchanged (shape + color for a11y). Updated tests, docs,
story, CLI template, and Table Lab wording accordingly.
@humbertovirtudes
humbertovirtudes force-pushed the navi/feat/table-row-status branch from 19ce3e2 to 490c4ab Compare July 17, 2026 17:08
@github-actions
github-actions Bot requested a review from ernestt July 17, 2026 17:08
@humbertovirtudes

Copy link
Copy Markdown
Contributor Author

Good call @ernestt, switched it. The default indicator is now a centered status dot rather than the full-height leading-edge bar, which addresses both concerns:

  1. No longer color-only by default when you opt into shape: the icon field renders an Icon instead of the dot (shape + color), same as before. For the plain dot, label supplies the accessible name via role="img".
  2. No longer reads as the tab selection state — the vertical bar shape is gone; a dot matches the original status-dot / icon pattern.

getStatus still returns {color, icon?, label?} (or null). Dot by default, icon when you pass one. Updated the tests, docs, story, CLI template, and Table Lab accordingly. Rebased on latest main and green.

@ernestt

ernestt commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Following up with the specific conventions behind my two worries — both trace directly to the Design Conventions wiki, so this is less a taste call than a convention gap.

1. Status by color alone. The System Interaction States section states it as a hard rule: "Status always pairs a semantic token with an icon — never color alone." The Color foundation flags the same thing as a smell (🟡 "Color alone — status by hue with no icon/text"). Right now icon and label are both optional, so the default/minimal usage ({color: 'error'}) is exactly the anti-pattern — and one of the unit tests deliberately locks in the color-only path. The icon addition made compliance possible; I think we want it to be the default. Making the shape signifier primary (icon/dot) and the color secondary would close this.

2. Collision with the selection language + the edge-bar tell. Two conventions here:

  • Our approved "Selected" representations already claim the leading edge bar: Selected (Edge style) is "--color-accent edge bar / underline marks the current item" (tabs/steps/entries) and Selected (Depressed style) covers list & nav rows. A colored full-height bar on a row's leading edge reuses the system's established "this is the current/selected item" channel to mean "status" — which cuts against "use the system's established channel for a given meaning." A red leading bar reads as "selected row," not "errored row."
  • The Radius foundation lists 🔴 "Side-tab accent — a thick colored stripe on one edge… (a classic AI tell)" as a smell in its own right.

The overarching principle is "Minimize visuals per state… Reuse an existing representation before inventing a new one" — which is really why I asked why we diverged from the original dot/icon version. That version was already convention-compliant: a leading status icon/dot (shape + semantic token) signals per-row status without color-alone and without borrowing the selection edge-bar. I'd lean toward going back to that as the primary treatment, keeping the color as reinforcement rather than the sole signal.

@humbertovirtudes

Copy link
Copy Markdown
Contributor Author

@ernestt should be good to land now yes?

@cixzhang cixzhang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

image

Thanks for making the updates! This is starting to look closer to what we have internally. @ernestt does this address your comments?

I left a few more items for your consideration. Main blocking one is whether we should force label to be required. I'm not sure if there's another way we can ensure accessible context for unlabeled statuses.

/** Optional icon rendered as the signifier instead of the dot (shape as an a11y differentiator). */
icon?: IconName;
/** Accessible label; announced via role="img". Recommended. */
label?: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm do we want to make this required instead? Without the label I'm not sure how we would make the status accessible for AT users.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — made it required. label is now a required field on TableRowStatus, so a status can never be conveyed by color alone; it's announced to assistive tech via role="img" and also shown on hover. Updated the type, the render path, the doc, and the tests in 90a6088.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes this looks good from my side. Thanks for fixing!

{...stylex.props(styles.wrap)}
role={role}
aria-label={status.label}
title={status.label}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What do you think about applying tooltip instead of the native title for more visual consistency?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — switched from the native title to the Tooltip component for visual consistency with the rest of the system. The indicator (dot or icon) is now wrapped in <Tooltip content={label}>. In 90a6088.

Make the accessible label required so a row status is never conveyed by
color alone, and render it through Tooltip instead of the native title
attribute for visual consistency. Addresses review feedback.
Surface the legal color values and icon names in the getStatus prop
doc instead of naming the TableRowStatusColor and IconName aliases, so
a reader without an IDE can see what is valid.
# Conflicts:
#	packages/core/src/Table/index.ts
@github-actions github-actions Bot removed the needs:design-review Affects visuals — Design should review label Jul 23, 2026
@humbertovirtudes
humbertovirtudes merged commit e265492 into facebook:main Jul 23, 2026
18 of 19 checks passed
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.

3 participants