Skip to content

Move every tab bar onto one shared tabs component - #5233

Draft
lmac-1 wants to merge 18 commits into
design-systemfrom
tabs-refactor
Draft

lmac-1 wants to merge 18 commits into
design-systemfrom
tabs-refactor

Conversation

@lmac-1

@lmac-1 lmac-1 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Description

This PR puts every tab bar in the app on one shared tabs component, with one stylesheet that both React and LiveView use.

  • Before, tabs were built five different ways: the editor's React Tabs, Headless UI in workflow health, tab_bar.ex and tabbed.ex in LiveView, and hand-rolled buttons in the credential form. Each was styled on its own.
  • React now uses assets/js/ui/Tabs.tsx and LiveView uses <.tabs> from lib/lightning_web/components/ui/tabs.ex. Both use the .ui-tabs and .ui-tab classes in assets/css/ui/tabs.css. Options: underline or pills, default or small, horizontal or vertical, icons, and disabled with a reason.
  • LiveView <.tabs> has two modes. In link mode, each tab goes to its own URL (History). In hash mode, tabs switch panels on the same page by URL hash (run page, project settings).
  • Moved over: the run panel and the full-screen code editor (React), workflow health (React), and History, the run page and project settings (LiveView). tab_bar.ex, tabbed.ex and the old editor Tabs.tsx are deleted.
  • Screen reader fixes. Every tab list now has a name ("Step details", "Project settings", "History"). A disabled tab keeps its own name and reads its reason after it, so you hear "Input, tab, dimmed, A valid step must be selected…" instead of only the reason. It can also be focused, so keyboard users can see the tooltip.
  • Credential environments in the credential form now look like the other tabs and are marked up as tabs. They still use their own markup, not the component, and a comment marks them as deprecated, since environments are due a redesign.
  • Workflow health tabs no longer move with the arrow keys. Press Tab to reach each one.
  • /dev/components shows each LiveView variant next to its React version.

Closes CON-325

Validation steps

  1. Open /dev/components. Each row shows the LiveView tabs on the left and React on the right. They should look the same.
  2. Open a run from History. Click Log, Input and Output. The panel switches and the URL hash changes. With no step selected, Input and Output are greyed out and hovering shows why.
  3. Open project settings. The tabs run down the side. Each one switches section, and reloading keeps you on the same section.
  4. With channels enabled, open History. Work Orders and Channel Logs each go to their own URL.
  5. Open workflow health. Work orders and Runs switch the charts.
  6. In the collaborative editor, open the run panel (Empty, New, Existing) and the full-screen code editor. Both tab sets switch as before.
  7. Add a credential and find "Credential environments". The "+" adds an environment, the tabs switch between them, and the purple underline sits on the grey line.
  8. With VoiceOver on, open a run with no step selected and press Tab to reach Input. You should hear "Input", then the reason.
  9. Run mix test test/lightning_web/components/ui/tabs_test.exs, then cd assets && npx vitest run test/ui/Tabs.test.tsx test/hooks/TabbedContainer.test.ts.

Additional notes for the reviewer

  1. Known gap: no arrow-key navigation in either stack. React tabs also have no id or aria-controls, and only workflow health wraps its content in tab panels. Both wait on deciding which tab bars are really tabs. Project settings reads more like side navigation, and the run panel's Empty, New and Existing reads more like a choice between options.
  2. Known gap: in React, switching a tab between enabled and disabled loses keyboard focus, because the tooltip wrapper remounts the button. No React tab is disabled today, so nothing hits it yet.
  3. assets/css/ui/tokens.css is a placeholder exported from Figma. The token format isn't final, so the script that generated it was removed from this branch.
  4. No Headless UI or react-aria. The components are plain markup that covers the basics, which keeps them small while the design settles. react-aria was used as a reference.
  5. Link-mode tabs render a <nav> with aria-current="page", not role="tab", because each one goes to a different URL.
  6. The LiveView Tooltip hook now also reads its text from a <template data-tooltip-content> when there's no aria-label. Existing tooltips all have an aria-label, so they're unaffected.
  7. Known quirks carried over unchanged: stray spaces in " Output ", "New" vs "Custom" labels in the run panel, a URL hash can open a disabled tab, nothing sends push-hash, and RunViewerPanel ignores onTabChange.

AI Usage

Please disclose whether you've used AI anywhere in this PR (it's cool, we just want to know!):

  • I have used Claude Code
  • I have used another model
  • I have not used AI

You can read more details in our Responsible AI Policy

Pre-submission checklist

  • I have performed an AI review of my code (we recommend using /review with Claude Code)
  • I have implemented and tested all related authorization policies. (e.g., :owner, :admin, :editor, :viewer)
  • I have updated the changelog.
  • I have ticked a box in "AI usage" in this PR

Comment thread assets/css/ui/tabs.css
@@ -0,0 +1,60 @@
/* Shared tabs styling. Both stacks emit .ui-tabs and .ui-tab.

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.

This is an example css file that the shared component will use. I don't have the proper design tokens yet so not clear on how the variables will slot in. Appreciate any guidance.

Comment thread assets/js/ui/Tabs.tsx Outdated
lmac-1 added 8 commits October 8, 2026 07:56
Build dev showcases only outside prod, from a glob over js/dev.
Share one panels helper across tab orientations, and pass the
extra class to the tab list explicitly. Use cn() in the showcase.
An empty panel list fell through to patch mode and raised on :patch.
Also point the vertical test at grow, which the formatter now writes.
Hash mode tab lists had no accessible name, and every patch mode one
was called "Tabs". Add a label attr and set it on each page.
The reason used to replace the tab's name, because the Tooltip hook only
read aria-label. The hook now also starts from a tooltip template, so
the tab keeps its label, links the reason with aria-describedby, and
takes focus so keyboard users can see the tooltip.
Mark them up as a named tablist so screen readers announce them as
tabs. They stay off the shared component and are commented as
deprecated, since environments are due a redesign.
@lmac-1 lmac-1 changed the title Tabs refactor first try Move every tab bar onto one shared tabs component Oct 8, 2026

This branch has not been deployed

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

Labels

None yet

Projects

Status: New Issues

Development

Successfully merging this pull request may close these issues.

1 participant