mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
## Problem The Icon only button example lacks some accessibility features: - no `aria-label` for screen readers - no tooltip for sighted users ## Solution Add both with comments explaining the reasons ## Review instructions See https://design-system-imfc534k0-supabase.vercel.app/design-system/docs/components/button#only-an-icon <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Clarified icon-only button guidance: use a tooltip for sighted users and an accessible label for screen readers. When the tooltip repeats the button’s label, prevent it from being announced twice. * Added guidance to use a square button container and increase the tap target by 8px. Updated the icon-button example to demonstrate a “View logs” tooltip and accessible labeling. * **New Features** * Icon-only buttons now use a compact square layout with an expanded tap target. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Danny White <3104761+dnywh@users.noreply.github.com>
198 lines
7.4 KiB
Plaintext
198 lines
7.4 KiB
Plaintext
---
|
|
title: Button
|
|
description: Displays a button or a link that looks like a button.
|
|
featured: true
|
|
component: true
|
|
---
|
|
|
|
<ComponentPreview name="button-default" 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
|
|
|
|
#### Default
|
|
|
|
Used when no `variant` is specified. Prefer this unless another variant fits better, as below.
|
|
|
|
<ComponentPreview name="button-default" />
|
|
|
|
#### Primary
|
|
|
|
Use sparingly for data insertion, confirming purchases, and other strong positive actions. Because it is so prominent, aim for at most one primary button in a viewport.
|
|
|
|
<ComponentPreview name="button-demo" />
|
|
|
|
#### 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" />
|
|
|
|
### Icon-only
|
|
|
|
Render an [icon](./icons) in a button without accompanying text. Ensure the button is accessible by:
|
|
|
|
- Wrapping it in a [Tooltip](./tooltip) for sighted users.
|
|
- Adding an `aria-label` prop for screen readers.
|
|
- Setting its `aria-describedby` prop to `undefined` when the tooltip content repeats the label. Otherwise screen readers read the label twice.
|
|
|
|
Consider also squaring off the button container as shown in the example below. For the default `tiny` size (`h-[26px]`), use `w-6.5` so the button is square.
|
|
|
|
Compact icon-only buttons may also benefit from the `hit-area` utility. This increases their tap target slightly each side without changing the visual layout. See the [Table](./table#actions) component for more information.
|
|
|
|
<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.
|
|
|
|
## Default fill and floating buttons
|
|
|
|
The default variant is meant to read as raised chrome on whatever surface it sits on.
|
|
|
|
- **Light:** opaque `bg-card` (a solid elevated plate). Opaque on purpose so the button can cover busy content underneath.
|
|
- **Dark:** translucent `bg-muted` (a foreground wash). That adapts to the local surface, but content can show through the fill.
|
|
|
|
Hover: light uses `hover:bg-muted`; dark uses `dark:hover:bg-accent`.
|
|
|
|
### Floating over content
|
|
|
|
If a default button is absolutely or sticky-positioned over code, tables, maps, or other busy UI, wrap it in [`FloatingPlate`](#floating-plate) so nothing bleeds through:
|
|
|
|
<ComponentPreview name="button-floating-plate" peekCode />
|
|
|
|
Keep positioning, z-index, and hover/focus reveal on the plate's `className`. Use `rounded="full"` when the child is a pill. For clusters (split toggles, parallel actions), wrap the group once.
|
|
|
|
Do not override the button fill with `bg-popover` at the callsite. The plate owns occlusion; the button stays a normal default control.
|
|
|
|
## Floating plate
|
|
|
|
`FloatingPlate` is an opaque `bg-popover` shell for floating default buttons (and small clusters).
|
|
|
|
```tsx
|
|
import { Button, FloatingPlate } from 'ui'
|
|
```
|
|
|
|
| Prop | Default | Notes |
|
|
| ----------- | ------- | --------------------------------------------------------------- |
|
|
| `rounded` | `lg` | `md`, `lg`, or `full`. Match or exceed the child button radius. |
|
|
| `className` | — | Positioning, opacity, gaps for clusters. |
|
|
|
|
See [Floating over content](#floating-over-content) for when to use it.
|
|
|
|
## Accessibility
|
|
|
|
[Keyboard focus](../accessibility#focus-management) is automatically handled:
|
|
|
|
- Enabled buttons default to `tabIndex={0}` (keyboard accessible)
|
|
- Buttons with native `disabled` 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` for buttons using native `disabled`.
|
|
|
|
When a disabled action has a non-obvious reason and needs a tooltip, add `focusableWhenDisabled` so keyboard users can still focus the control. See [Disabled controls](../accessibility#disabled-controls).
|
|
|
|
### Focusable when disabled
|
|
|
|
Use `focusableWhenDisabled` with `disabled` when the action is blocked for a non-obvious reason and you need a tooltip or other explanation. The control stays in the tab order and uses `aria-disabled` instead of native `disabled`.
|
|
|
|
```tsx
|
|
<Button disabled focusableWhenDisabled>
|
|
Pause project
|
|
</Button>
|
|
```
|
|
|
|
In Studio, `ButtonTooltip` adds `focusableWhenDisabled` to disabled buttons with tooltip text automatically.
|