Skip to content

feat(Tabs): add trigger orientation and overflow handling#6488

Open
mikenewbon wants to merge 10 commits into
nuxt:v4from
mikenewbon:feat/tabs-trigger-orientation-overflow
Open

feat(Tabs): add trigger orientation and overflow handling#6488
mikenewbon wants to merge 10 commits into
nuxt:v4from
mikenewbon:feat/tabs-trigger-orientation-overflow

Conversation

@mikenewbon
Copy link
Copy Markdown
Contributor

🔗 Linked issue

Resolves #6487

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

Adds two new props to UTabs for richer tab list layouts:

  • trigger-orientation — stack icon/avatar above the label (vertical), with theme defaults tuned for compact stacked labels (text-[10px]/3, tighter gap/padding).
  • overflow — handle tabs that don't fit:
    • scroll — scroll along the list axis
    • wrap — wrap onto multiple lines (custom 2D indicator)
    • collapse — hide overflow behind a More dropdown (more-label, more-icon, #more slot)

Also adds #list-leading / #list-trailing slots, splits the list into outer list + inner tablist wrappers (so the More dropdown sits outside role="tablist" for accessibility), and uses a unified custom indicator in wrap/collapse modes so the active pill animates smoothly between visible tabs and More.

Includes docs examples, playground controls, and expanded tests/snapshots.

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

mikenewbon and others added 7 commits May 22, 2026 16:58
- `triggerOrientation: 'horizontal' | 'vertical'` stacks the trigger
  icon/avatar above the label.
- `overflow: 'scroll' | 'wrap' | 'collapse'` handles tab lists that
  exceed their container. `wrap` enables flex wrapping (the indicator
  is hidden since a single-track indicator can't represent multiple
  rows). `collapse` measures triggers via ResizeObserver and tucks
  overflowing items into a "More" dropdown; works for both horizontal
  and vertical orientations.
- Adds `moreLabel`/`moreIcon` props (defaults: "More" /
  `appConfig.ui.icons.ellipsis`) and a `more` slot that overrides the
  trigger content while keeping the dropdown behavior.

Defaults preserve existing behavior: `triggerOrientation` defaults to
`horizontal` and `overflow` is unset.
…ndling

- Introduced `triggerOrientation` and `overflow` props for better tab management.
- Added a new `manyItems` array to demonstrate multiple tab items with icons and content.
- Updated the template to include new select options for `triggerOrientation` and `overflow`.
- Enhanced the tab component to support a custom indicator that adapts based on overflow settings.
- Updated the `wrap` overflow option to allow for visible indicators and added styles for better visual feedback.
- Refactored the logic for managing overflow items and custom indicators, ensuring proper visibility and positioning.
- Adjusted the template to conditionally render the custom indicator based on the overflow state.
- Updated the tab component's class bindings to include additional conditions for measuring and collapse states.
- Removed the specific class for the collapse trigger in the theme configuration, simplifying the styling logic.
- Enhanced the visual responsiveness of the tab triggers when measuring and in collapse mode.
- Introduced new examples demonstrating the `overflow` prop with options for `collapse`, `scroll`, and `wrap`.
- Added an example for `triggerOrientation` to showcase vertical tab layouts.
- Updated documentation to include new examples and usage details for the `overflow` and `trigger-orientation` props.
Move the collapse overflow dropdown outside TabsList to satisfy tablist
ARIA child requirements, add a tablist theme slot for scroll/wrap, fix
data-slot on the More default content, and document class/ui props with
variant and collapse accessibility tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
Refactor the class bindings for the More trigger to utilize the ui.more method for better consistency. Adjust the tablist styling to include min-h-0 for improved layout handling. Update snapshots to reflect these changes in the Tabs component's rendering.
@mikenewbon mikenewbon requested a review from benjamincanac as a code owner May 22, 2026 16:25
@github-actions github-actions Bot added the v4 #4488 label May 22, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Adds trigger-orientation and overflow props to UTabs with theme variants and compound rules, implements measurement-driven collapse/wrap behavior and a custom positioned indicator, exposes a "More" dropdown and a 'more' slot, updates emits typing, expands tests (including accessibility), adds documentation and examples, and wires interactive controls in the playground.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(Tabs): add trigger orientation and overflow handling' directly and clearly summarizes the main changes—adding trigger orientation and overflow handling to the Tabs component.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset, detailing the new props, overflow modes, structural changes, and documentation updates.
Linked Issues check ✅ Passed The PR implements all key requirements from #6487: adds vertical trigger orientation support, implements overflow handling (scroll/wrap/collapse), ensures indicator behavior across modes, and improves Tabs flexibility.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue #6487: adding trigger orientation, overflow handling, examples, documentation, tests, and theme updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/theme/tabs.ts

Parsing error: Unexpected token {


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/runtime/components/Tabs.vue (1)

396-399: 💤 Low value

Redundant setupCustomIndicator() call causes unnecessary observer churn.

When activeValue, isOverflowActive, or visibleCount changes, only the indicator position needs updating—the ResizeObserver was already set up by onMounted or the props watch. Currently, every tab selection disconnects and reconnects the observer, and updateCustomIndicator() is called twice (once inside setupCustomIndicator() at line 353, and again explicitly).

Suggested simplification
 watch([activeValue, isOverflowActive, visibleCount], () => {
-  setupCustomIndicator()
   updateCustomIndicator()
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/components/Tabs.vue` around lines 396 - 399, The watch on
[activeValue, isOverflowActive, visibleCount] should not call
setupCustomIndicator() because that re-installs the ResizeObserver
unnecessarily; instead remove the setupCustomIndicator() call and only invoke
updateCustomIndicator() from that watcher so we only reposition the indicator on
value/overflow/visibility changes and rely on onMounted and the props watch to
initialize the observer via setupCustomIndicator(). Ensure the watcher
references the same reactive symbols (activeValue, isOverflowActive,
visibleCount) and leave setupCustomIndicator() calls in onMounted and the
props-related watch unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/content/docs/2.components/tabs.md`:
- Around line 258-285: The new overflow section uses inconsistent/setext-style
headings and component-example blocks causing markdownlint MD003/MD001; update
the three headings ("Scroll", "Wrap", "Collapse") to consistent ATX-style (e.g.,
"### Scroll", "### Wrap", "### Collapse") and ensure they follow the correct
heading level sequence used in this doc, and if lint still flags, convert the
offending ::component-example blocks (names 'tabs-overflow-scroll-example',
'tabs-overflow-wrap-example', 'tabs-overflow-collapse-example') to the same
directive syntax used elsewhere in the file so the `---` metadata isn’t parsed
as setext headings.

---

Nitpick comments:
In `@src/runtime/components/Tabs.vue`:
- Around line 396-399: The watch on [activeValue, isOverflowActive,
visibleCount] should not call setupCustomIndicator() because that re-installs
the ResizeObserver unnecessarily; instead remove the setupCustomIndicator() call
and only invoke updateCustomIndicator() from that watcher so we only reposition
the indicator on value/overflow/visibility changes and rely on onMounted and the
props watch to initialize the observer via setupCustomIndicator(). Ensure the
watcher references the same reactive symbols (activeValue, isOverflowActive,
visibleCount) and leave setupCustomIndicator() calls in onMounted and the
props-related watch unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3d8e0b0b-f884-42c8-b624-3d7ce201ea05

📥 Commits

Reviewing files that changed from the base of the PR and between 09f9cd0 and e2fa46e.

⛔ Files ignored due to path filters (2)
  • test/components/__snapshots__/Tabs-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Tabs.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (9)
  • docs/app/components/content/examples/tabs/TabsOverflowCollapseExample.vue
  • docs/app/components/content/examples/tabs/TabsOverflowScrollExample.vue
  • docs/app/components/content/examples/tabs/TabsOverflowWrapExample.vue
  • docs/app/components/content/examples/tabs/TabsTriggerOrientationExample.vue
  • docs/content/docs/2.components/tabs.md
  • playgrounds/nuxt/app/pages/components/tabs.vue
  • src/runtime/components/Tabs.vue
  • src/theme/tabs.ts
  • test/components/Tabs.spec.ts

Comment thread docs/content/docs/2.components/tabs.md Outdated
Normalize overflow docs headings to ATX style with inline component
examples, and stop reconnecting the custom indicator ResizeObserver on
every tab selection.

Co-authored-by: Cursor <cursoragent@cursor.com>
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 22, 2026

npm i https://pkg.pr.new/@nuxt/ui@6488

commit: a33e96d

Eliminated the sections detailing the `class` and `ui` props from the Tabs documentation to streamline content and focus on relevant examples. This change enhances clarity and reduces redundancy in the documentation.
Move the scroll overflow handler to the outer list slot so the absolute
indicator scrolls with the tabs while preserving the list padding that
gives the indicator its proper inset-y height. Reverts the tablistRef
indicator detour and restores the v4 single-indicator template.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/theme/tabs.ts (1)

175-180: 💤 Low value

Consider adding orientation: 'horizontal' for consistency.

While the component already defaults orientation to 'horizontal' via withDefaults, including it in defaultVariants would make the theme configuration more self-documenting and align with how triggerOrientation is handled (defaulted in both places).

📝 Optional addition
 defaultVariants: {
   color: 'primary',
   variant: 'pill',
   size: 'md',
+  orientation: 'horizontal',
   triggerOrientation: 'horizontal'
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/theme/tabs.ts` around lines 175 - 180, Add orientation: 'horizontal' to
the defaultVariants object in the tabs theme so the theme explicitly documents
the same default applied via withDefaults; update the defaultVariants block
(which currently contains color, variant, size, triggerOrientation) to include
orientation: 'horizontal' to match the withDefaults setting and keep
triggerOrientation handled consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/theme/tabs.ts`:
- Around line 175-180: Add orientation: 'horizontal' to the defaultVariants
object in the tabs theme so the theme explicitly documents the same default
applied via withDefaults; update the defaultVariants block (which currently
contains color, variant, size, triggerOrientation) to include orientation:
'horizontal' to match the withDefaults setting and keep triggerOrientation
handled consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d09322b7-f414-4a85-93d5-a448a7cb1852

📥 Commits

Reviewing files that changed from the base of the PR and between bb3c515 and a33e96d.

⛔ Files ignored due to path filters (2)
  • test/components/__snapshots__/Tabs-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Tabs.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (1)
  • src/theme/tabs.ts

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

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add apple-style trigger orientation to UTabs, add overflow options for screen flexability

1 participant