Files
Danny White 14fe0c0cc8 fix(studio): slightly round split-button corners on focus (#49129)
## What kind of change does this PR introduce?

UI polish for split buttons (primary action + dropdown chevron).
Follow-up to #49055.

## What is the current behavior?

The focus ring sits above the neighbouring half, but the inner edge
stays square, so the ring has two sharp corners at the join.

## What is the new behavior?

On keyboard focus, the squared-off edge uses a slight radius so the ring
matches the outer corners more closely. Resting state is unchanged.
Split-button callsites now share the same join classes as the
design-system example.

| Before | After |
| --- | --- |
| <img width="1030" height="296" alt="43471"
src="https://github.com/user-attachments/assets/9df3bd72-c7ac-4419-ae18-a7e649dc2d66"
/> | <img width="1056" height="276" alt="CleanShot 2026-08-17 at 10 45
09@2x"
src="https://github.com/user-attachments/assets/52e8a4dc-9c52-45ce-b4d0-f0e7b1b75935"
/> |

## To test

Tab to each half (labelled button, then chevron). Inner corners of the
focus ring should be slightly rounded, not square.

1. [Split with
dropdown](https://design-system-git-fix-split-button-focus-radius-supabase.vercel.app/design-system/docs/components/button#split-with-dropdown)
(no login)
2. [Access
Tokens](https://studio-staging-git-fix-split-button-focus-radius-supabase.vercel.app/dashboard/account/tokens)
→ Generate new token
3. Any project on [studio
staging](https://studio-staging-git-fix-split-button-focus-radius-supabase.vercel.app/dashboard/_/settings/general)
→ Settings → General → Restart project

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

## Summary by CodeRabbit

- **Accessibility**
  - Added accessible labels to dropdown and export controls.
- Improved keyboard-focus visibility, layering, and rounded edge
treatment across joined buttons and menus.
  - Removed misleading or redundant screen-reader text and titles.

- **Bug Fixes**
- Prevented split-button controls from shrinking or displaying awkward
borders and corners.
- Refined hover and focus behavior for action buttons throughout
settings, database, storage, account, and documentation interfaces.

- **Documentation**
- Clarified guidance for using overflow menus and responsive
split-button actions.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-17 17:28:22 +10:00

151 lines
4.7 KiB
Plaintext

---
title: Button
description: Displays a button or a link that looks like a button.
featured: true
component: true
---
<ComponentPreview name="button-demo" peekCode wide />
## Usage
```tsx
import { Button } from '@/components/ui/button'
```
```tsx
<Button variant="outline">Button</Button>
```
## Link
You can use the `buttonVariants` helper to create a link that looks like a button.
```tsx
import { buttonVariants } from '@/components/ui/button'
```
```tsx
<Link className={buttonVariants({ variant: 'outline' })}>Click here</Link>
```
Alternatively, you can set the `asChild` parameter and nest the link component.
```tsx
<Button asChild>
<Link href="/login">Login</Link>
</Button>
```
## Examples
### Sizes
Use the `size` prop to determine the size of the button.
<ComponentPreview name="button-sizes" />
### Variants
These are all the different `variant` variations.
#### Primary
Used for data insertion actions, confirming purchases, strong positive actions.
<ComponentPreview name="button-demo" />
#### Default
Used for opening dialogs, navigating to pages, and other non CRUD actions.
This `variant` will probably be the most used button variant.
It will probably be changed to be the default variant in future.
<ComponentPreview name="button-default" />
#### Secondary
Can be used for signaling a data or config change, but not as serious as a primary button.
For destructive or side effect actions, use the `destructive` or `warning` variant.
<ComponentPreview name="button-secondary" />
#### Warning
Used for actions that might have a side effect, but not as serious as a destructive action.
<ComponentPreview name="button-warning" />
#### Destructive (currently `danger`)
Used for actions that will have a serious destructive side effect, like deleting data.
prop `variant` will probably be changed to `destructive` in the future.
<ComponentPreview name="button-destructive" />
#### Outline
Used for secondary actions, or actions that are not as important as the primary action.
<ComponentPreview name="button-outline" />
#### Ghost (currently `text`)
Used for actions that are not as important as the primary action, or for actions that are not as important as the primary action.
prop `variant` will probably be changed to `ghost` in the future.
<ComponentPreview name="button-ghost" />
#### Link
Used for actions that are not as important as the primary action, or for actions that are not as important as the primary action.
<ComponentPreview name="button-link" />
### Only an icon
Displaying only an Icon in a button.
<Admonition type="note" title="This feature requires more support" className="mt-3">
We should update the button component to support this use case better.
</Admonition>
<ComponentPreview name="button-icon" />
### 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 />
Ensure the middle border is shared rather than doubled-up. Do not use `border-l-0` on the chevron button as that drops the divider on hover/focus. Instead:
- Primary action: `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.
- Both: `focus-visible:z-10` so the focus ring stacks above the neighbour, and `focus-visible:rounded-r-sm` / `focus-visible:rounded-l-sm` so the squared-off edge is slightly rounded while the ring is shown.
- Chevron trigger only: `aria-label` describing the menu (the icon is decorative).
Inside [Admonition](../fragments/admonition#split-button-with-dropdown) actions when `layout="responsive"`: also use `flex w-full @lg:w-auto` with `flex-1 @lg:flex-none` on the primary action.
## Accessibility
[Keyboard focus](../accessibility#focus-management) is automatically handled:
- Enabled buttons default to `tabIndex={0}` (keyboard accessible)
- Disabled buttons default to `tabIndex={-1}` (removed from tab order)
- You can still override with an explicit `tabIndex` prop when needed
- Keyboard focus uses the shared `focus-ring` utility; variants do not change ring colour
You therefore don't need to manually set `tabIndex`, as Button handles it automatically based on its `disabled` state.