Skip to content

feat(Button): add orientation prop - #6882

Open
J-Michalek wants to merge 1 commit into
nuxt:v4from
J-Michalek:feat/button-orientation
Open

feat(Button): add orientation prop#6882
J-Michalek wants to merge 1 commit into
nuxt:v4from
J-Michalek:feat/button-orientation

Conversation

@J-Michalek

@J-Michalek J-Michalek commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

🔗 Linked issue

Resolves #6527

❓ 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 an orientation prop to Button that controls how the icon and label are laid out inside the button. vertical stacks them — icon on top, label below — which is the layout you want for mobile tab bars, toolbars, and card-style buttons. Until now this needed a wrapper component or an override of ui.base, which breaks whenever the theme changes.

<template>
  <!-- horizontal (default, unchanged) -->
  <UButton icon="i-lucide-home" label="Home" />

  <!-- vertical: icon above label -->
  <UButton icon="i-lucide-home" label="Home" orientation="vertical" />
</template>

This follows the orientation pattern already used by 30 components; User and InputRating do the same thing — a leaf component flipping its own root flex direction. The default is horizontal, so existing buttons render identically.

Notes on the implementation:

  • orientation is separate from the fieldGroup variant. Button already consumed an orientation from a wrapping <UFieldGroup> via useFieldGroup, used only to join border radii. That local is now fieldGroupOrientation, so the group keeps driving radius joining while the new prop drives content layout. The two are independent: a vertical <UFieldGroup> still yields horizontal buttons with correctly joined corners, and a vertical button inside a horizontal group stacks its content while keeping the group's side-by-side radii.
  • block + orientation="vertical" needed a fix to compose. The block variant sets trailingIcon: 'ms-auto' to push the trailing icon to the end of the main axis. In a column that becomes a cross-axis margin and shoves the icon to one side, so a block + vertical compound variant resets it to ms-0.
  • truncate needed a width constraint. In a row the label is a shrinkable flex item, so truncate works. In a column it is sized to its content, so truncate silently does nothing and long labels overflow the padding box. The vertical branch adds max-w-full (plus text-center, which also keeps wrapped labels centered under the icon for anyone overriding whitespace-normal).
  • data-orientation on the root, matching User, Alert, Toast and others. This is why the snapshot diff is large: every rendered Button gains the attribute. No classes changed — verified by diffing all ~900 affected snapshots with the attribute stripped, which comes out byte-identical.

One thing deliberately left out: the size variants keep their horizontal-biased padding (px-2.5 py-1.5 at md), so stacked buttons read a little wide. Retuning that means five size × orientation compound variants and an opinionated call on what stacked padding should be. It is tunable meanwhile via ui.base or app.config.ui.button.

📝 Checklist

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

Add an `orientation` prop that controls how the icon and label are laid
out inside the button. `vertical` stacks them, icon above label.

The local `orientation` from `useFieldGroup` is renamed to
`fieldGroupOrientation` so the group keeps driving border-radius joining
while the new prop drives content layout.

Also resets `block`'s `trailingIcon: ms-auto` to `ms-0` when vertical,
where it would otherwise become a cross-axis margin, and constrains the
label with `max-w-full` so `truncate` still applies in a column.

Resolves nuxt#6527
@github-actions github-actions Bot added the v4 #4488 label Aug 25, 2026
@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fafd8be6-eeba-4e5a-9cd6-c218873fc47d

📥 Commits

Reviewing files that changed from the base of the PR and between bb55709 and 7388a4b.

⛔ Files ignored due to path filters (80)
  • test/components/__snapshots__/Alert-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Alert.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/AuthForm-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/AuthForm.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Banner-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Banner.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Button-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Button.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Calendar-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Calendar.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Carousel-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Carousel.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/ChatPromptSubmit-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/ChatPromptSubmit.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/ChatTool-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/ChatTool.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/CommandPalette-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/CommandPalette.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/DashboardNavbar-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/DashboardNavbar.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/DashboardSearch-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/DashboardSearch.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/DashboardSearchButton-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/DashboardSearchButton.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/DashboardSidebar-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/DashboardSidebar.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/DashboardSidebarCollapse-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/DashboardSidebarCollapse.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/DashboardSidebarToggle-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/DashboardSidebarToggle.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Drawer-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Drawer.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/EditorDragHandle-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/EditorDragHandle.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/EditorToolbar-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/EditorToolbar.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Empty-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Empty.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Error-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Error.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/FieldGroup-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/FieldGroup.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/FileUpload-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/FileUpload.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Header-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Header.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/InputMenu-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/InputMenu.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/InputNumber-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/InputNumber.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Modal-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Modal.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/PageCTA-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/PageCTA.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/PageHeader-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/PageHeader.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/PageHero-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/PageHero.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/PageSection-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/PageSection.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Pagination-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Pagination.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/PricingPlan-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/PricingPlan.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/PricingTable-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/PricingTable.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/SelectMenu-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/SelectMenu.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Sidebar-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Sidebar.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Slideover-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Slideover.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Table-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Table.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Theme-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Theme.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Toast-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Toast.spec.ts.snap is excluded by !**/*.snap
  • test/components/content/__snapshots__/ContentSearch.spec.ts.snap is excluded by !**/*.snap
  • test/components/content/__snapshots__/ContentSearchButton.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (5)
  • docs/content/docs/2.components/button.md
  • playgrounds/nuxt/app/pages/components/button.vue
  • src/runtime/components/Button.vue
  • src/theme/button.ts
  • test/components/Button.spec.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The Button component now supports a horizontal or vertical orientation prop. Vertical styling stacks content, centers labels, and adjusts trailing-icon margins for block buttons. Runtime wiring keeps button orientation separate from field-group orientation and exposes data-orientation. Tests cover theme-derived orientations and combined vertical button props. Documentation and the Nuxt playground include vertical examples.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 7388a

The change adds an optional Button orientation behavior while preserving the horizontal default, with documentation and snapshots updated; no actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding an orientation prop to Button.
Description check ✅ Passed The description explains the new orientation prop, its behavior, implementation details, compatibility considerations, and documentation updates.
Linked Issues check ✅ Passed The changes satisfy issue #6527. They add horizontal and vertical orientations, preserve the horizontal default, stack vertical content, separate button orientation from field-group orientation, handl…
Out of Scope Changes check ✅ Passed All reviewed changes support the linked issue. The source, theme, tests, playground examples, and documentation changes are directly related to the Button orientation feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
Full details: Linked Issues check

Explanation

The changes satisfy issue #6527. They add horizontal and vertical orientations, preserve the horizontal default, stack vertical content, separate button orientation from field-group orientation, handle block buttons, retain icon-only compatibility, add tests, and update documentation.

Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2 files. (3 skipped: 3 unsupported.)

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

Warning

Some tools did not complete. Review the errors below.

🔧 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.

playgrounds/nuxt/app/pages/components/button.vue

Parsing error: Unexpected token >

src/runtime/components/Button.vue

Parsing error: Unexpected token )

src/theme/button.ts

Parsing error: Unexpected token {

  • 1 others

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.

@codspeed-hq

codspeed-hq Bot commented Aug 25, 2026

Copy link
Copy Markdown

Merging this PR will regress 1 benchmark

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
❌ 1 regressed benchmark
✅ 28 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
toggle loading 28.1 ms 39.6 ms -29.08%
ULink (default slot) 10.9 ms 6 ms +81.64%
UButton (no link) 31.8 ms 25.4 ms +24.96%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing J-Michalek:feat/button-orientation (7388a4b) with v4 (bb55709)

Open in CodSpeed

@pkg-pr-new

pkg-pr-new Bot commented Aug 25, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/@nuxt/ui@6882

commit: 7388a4b

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.

feat(Button): add prop for vertical (stacked) layout

1 participant