docs(design-system): document split button with dropdown pattern (#47422)

## Summary
- Add `button-split-dropdown` and `admonition-button-split`
design-system examples for primary action + chevron menu.
- Document the shared middle border pattern (`-ml-px`, `hover:z-10`;
avoid `border-l-0`) in Button and Admonition docs.
- Mirrors the approach established in the custom SMTP email templates
admonition.

## Test plan
- [ ] Open design-system site and verify `Button → Split with dropdown`
preview renders correctly
- [ ] Open `Admonition → Split button with dropdown` preview and confirm
middle border looks correct on hover/focus
- [ ] Confirm peek code shows the documented class names

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added new split-button dropdown live demos, including
icon/chevron-triggered menus with actionable items.
* Added an admonition demo demonstrating a split primary button plus
dropdown trigger with responsive layout.

* **Documentation**
* Expanded “Split with dropdown” guidance with pairing/when-to-use
rules, required styling/border behavior, and responsive layout
instructions for the primary + chevron actions.
* Updated button documentation heading capitalization to match
typography standards.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Danny White authored and GitHub committed 2026-07-03 10:14:12 +10:00
1 parent 4f05124ce7
commit be01b38118
6 files changed
+161 -2

No files matched your search

+22
View File
@@ -159,6 +159,17 @@ export const Index: Record<string, any> = {
subcategory: "undefined",
chunks: []
},
"admonition-button-split": {
name: "admonition-button-split",
type: "components:example",
registryDependencies: ["admonition","button","dropdown-menu"],
component: React.lazy(() => import("@/registry/default/example/admonition-button-split")),
source: "",
files: ["registry/default/example/admonition-button-split.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"admonition-description-only": {
name: "admonition-description-only",
type: "components:example",
@@ -577,6 +588,17 @@ export const Index: Record<string, any> = {
subcategory: "undefined",
chunks: []
},
"button-split-dropdown": {
name: "button-split-dropdown",
type: "components:example",
registryDependencies: ["button","dropdown-menu"],
component: React.lazy(() => import("@/registry/default/example/button-split-dropdown")),
source: "",
files: ["registry/default/example/button-split-dropdown.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"calendar-demo": {
name: "calendar-demo",
type: "components:example",
@@ -105,7 +105,7 @@ Used for actions that are not as important as the primary action, or for actions
<ComponentPreview name="button-link" />
### Only an Icon
### Only an icon
Displaying only an Icon in a button.
@@ -115,12 +115,28 @@ Displaying only an Icon in a button.
<ComponentPreview name="button-icon" />
### As Child
### As child
Supports slot behavior with `asChild` prop.
<ComponentPreview name="button-as-child" />
### Split with dropdown
Pair a button with a chevron `DropdownMenu` trigger when there are variations of the same action, or alternative ways to accomplish the same goal. The default or most likely option should be used on the exposed button.
When secondary actions are related but distinct—not alternatives to the primary action—display the primary action as a button and place the rest in an overflow menu instead. See [Table multiple actions](./table#multiple-actions).
<ComponentPreview name="button-split-dropdown" peekCode />
The shared middle border is the tricky part. Do **not** use `border-l-0` on the chevron button — that drops the divider on hover/focus. Instead:
- Primary: `rounded-r-none` and `hover:z-10` so its border stacks above the chevron on hover.
- Chevron trigger: `rounded-l-none`, `shrink-0`, `px-[4px] py-[5px]`, and `-ml-px` to overlap the adjacent border by one pixel.
- Chevron trigger only: `aria-label` describing the menu (the icon is decorative).
Inside [Admonition](../fragments/admonition#split-button-with-dropdown) actions, also use `flex w-full @lg:w-auto` with `flex-1 @lg:flex-none` on the primary when `layout="responsive"`.
## Accessibility
[Keyboard focus](../accessibility#focus-management) is automatically handled:
@@ -46,6 +46,18 @@ Style the color of the button based upon the _context_ of the Admonition, not it
Only ever use the `primary` (green) button `type` on a `default` Admonition. Even then—given that Admonition is an isolated callout—the `primary` button `type` should rarely be used.
### Split button with dropdown
When a callout offers alternative ways to accomplish the same goal, use a [split button](../components/button#split-with-dropdown) instead of crowding the callout with multiple buttons. For related but distinct actions, use an overflow menu. See [Table multiple actions](../components/table#multiple-actions).
<ComponentPreview
name="admonition-button-split"
className="[&_.preview>[data-orientation=vertical]]:sm:max-w-[70%]"
peekCode
/>
In responsive Admonitions, wrap the pair in `flex w-full @lg:w-auto`, give the primary `flex-1 @lg:flex-none`, and keep the chevron `shrink-0`.
## Examples
<ComponentPreview
@@ -0,0 +1,60 @@
import { ChevronDown } from 'lucide-react'
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
export default function AdmonitionButtonSplitDemo() {
return (
<Admonition
type="default"
layout="responsive"
title="Set up custom SMTP to edit templates"
description="Emails will be sent using the default templates. Set up custom SMTP to edit their subject and body."
actions={
<div className="flex w-full @lg:w-auto">
<Button
type="button"
variant="default"
className="flex-1 rounded-r-none px-3 @lg:flex-none hover:z-10"
>
Set up SMTP
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
type="button"
variant="default"
aria-label="More email template editing options"
className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px"
icon={<ChevronDown />}
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52">
<DropdownMenuItem>
<div className="flex flex-col gap-y-0.5">
<p className="block text-foreground">Upgrade to Pro</p>
<p className="block text-foreground-lighter text-balance">
Customize templates while using Supabase’s email service
</p>
</div>
</DropdownMenuItem>
<DropdownMenuItem>
<div className="flex flex-col gap-y-0.5">
<p className="block text-foreground">Configure Send Email hook</p>
<p className="block text-foreground-lighter text-balance">
Send auth emails through your own workflow
</p>
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
}
/>
)
}
@@ -0,0 +1,37 @@
import { ChevronDown } from 'lucide-react'
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from 'ui'
export default function ButtonSplitDropdownDemo() {
return (
<div className="flex w-fit">
<Button type="button" variant="default" className="rounded-r-none hover:z-10">
Primary action
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
type="button"
variant="default"
aria-label="More actions"
className="shrink-0 rounded-l-none px-[4px] py-[5px] -ml-px"
icon={<ChevronDown />}
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuItem>Secondary action</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-destructive focus:text-destructive">
Destructive action
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
)
}
+12
View File
@@ -25,6 +25,12 @@ export const examples: Registry = [
registryDependencies: ['admonition'],
files: ['example/admonition-button.tsx'],
},
{
name: 'admonition-button-split',
type: 'components:example',
registryDependencies: ['admonition', 'button', 'dropdown-menu'],
files: ['example/admonition-button-split.tsx'],
},
{
name: 'admonition-description-only',
type: 'components:example',
@@ -253,6 +259,12 @@ export const examples: Registry = [
registryDependencies: ['button'],
files: ['example/button-as-child.tsx'],
},
{
name: 'button-split-dropdown',
type: 'components:example',
registryDependencies: ['button', 'dropdown-menu'],
files: ['example/button-split-dropdown.tsx'],
},
{
name: 'calendar-demo',
type: 'components:example',