mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 18:05:11 +03:00
## What kind of change does this PR introduce? UI polish / design system: refreshed button styles, related token tweaks, and a shared floating-button plate. Resolves DEPR-652. ## What is the current behavior? Default, primary, and secondary buttons use older fills, borders, and hover treatments. Primary still leans on brand scale utilities. Default fills don’t always read as raised chrome across surfaces, and floating copy / expand / scroll controls can let busy content show through translucent fills. Call sites hand-roll `rounded-* bg-background` wrappers for that. ## What is the new behavior? Refreshes primary, default, and secondary buttons with medium-weight labels, subtle shadows and inset edges, and smoother transitions. Light-mode default buttons use a raised fill with an accent hover state, primary text is brighter, and inline keyboard shortcuts inherit the button’s colour. Adds `FloatingPlate`: an opaque `bg-popover` shell for floating default buttons (and small clusters). Migrates Studio, Docs-related patterns, www, and `ui-patterns` floaters onto it so busy content no longer shows through translucent fills. Positioning, z-index, and hover/focus reveal stay on the plate’s `className`. Use `rounded="full"` for pills. Also: - Moves primary onto semantic `--primary` / `--primary-hover` (with a light-theme override) instead of brand utility fills - Tokenises button shadows as `--button-shadow-drop` / `--button-shadow-raised` / `--button-shadow-default` on the Button base - Aligns hover direction: darken on light mode, lighten on dark mode for both default and primary - Default fill stays opaque `bg-card` in light (occlusion) and translucent `bg-muted` in dark (adapts to the local surface) - Documents fills and `FloatingPlate` on the design-system Button page (with a live example) - Scales shared radius tokens in Studio and www; medium+ Button sizes use a proportionally softer radius - Fixes www nav CTA centering (`lg:inline-flex` instead of `lg:block`) - Query detail Expand/Collapse wires `aria-expanded` / `aria-controls` | Before | After | | --- | --- | | <img width="1074" height="438" alt="CleanShot 2026-09-18 at 15 52 51@2x" src="https://github.com/user-attachments/assets/ef43da21-b053-4b7e-9ac4-ab8b428228ab" /> | <img width="1090" height="464" alt="CleanShot 2026-09-18 at 15 50 59@2x" src="https://github.com/user-attachments/assets/2ddc55fc-4c8d-499c-a280-f3db3d99023c" /> | | <img width="1082" height="446" alt="CleanShot 2026-09-18 at 15 52 35@2x" src="https://github.com/user-attachments/assets/3dd5452d-325a-4e4a-a79d-26c6c6950a31" /> | <img width="1078" height="446" alt="CleanShot 2026-09-18 at 15 51 13@2x" src="https://github.com/user-attachments/assets/93666385-3e6e-42e0-9891-9cd6bb935b67" /> | ## To test ### Design system - [Button page](https://design-system-git-chore-button-styles-supabase.vercel.app/design-system/docs/components/button): default / primary in light and dark; hover should darken on light, lighten on dark - Same page: [Floating over content](https://design-system-git-chore-button-styles-supabase.vercel.app/design-system/docs/components/button#floating-over-content) / [Floating plate](https://design-system-git-chore-button-styles-supabase.vercel.app/design-system/docs/components/button#floating-plate) example; Copy over SQL should stay opaque - Spot-check hover on a code preview Copy control ### Docs [Docs deploy preview](https://docs-git-chore-button-styles-supabase.vercel.app/docs): - [Docs homepage](https://docs-git-chore-button-styles-supabase.vercel.app/docs): top-right **Sign up** / **Dashboard** primary; menu icon beside it (default icon button) - Shrink below `lg` and open the hamburger drawer: bottom **Sign in** (default) + **Start your project** (primary) medium block buttons - Tab once for **Skip to content** (FloatingPlate) - [MCP guide](https://docs-git-chore-button-styles-supabase.vercel.app/docs/guides/ai-tools/mcp): project picker - [Apple login](https://docs-git-chore-button-styles-supabase.vercel.app/docs/guides/auth/social-login/auth-apple): **Generate Secret Key** button in the Apple Secret Generator - Optional opacity check: any guide code block Copy control (e.g. at the bottom of [Import data into Supabase](https://docs-git-chore-button-styles-supabase.vercel.app/docs/guides/database/import-data)) ### Studio [Studio deploy preview](https://studio-staging-git-chore-button-styles-supabase.vercel.app/): - **Observability → Query Performance**: open a query detail → Expand/Collapse pill + SQL Copy chip (dark: no bleed-through) - **Observability → Query Insights**: select a query → Clear query pill - **Table Editor → any table → Definition** → floating **Open in SQL Editor** - **Connect → Framework → Add files**: Copy on the code tabs (FloatingPlate; light hover follow-up is DEPR-694) - Tab once for **Skip to content** ### WWW - [www deploy preview](https://zone-www-dot-com-git-chore-button-styles-supabase.vercel.app/): nav Sign in / Start your project vertical centering; hero medium CTAs radius --------- Co-authored-by: Danny White <3104761+dnywh@users.noreply.github.com>
194 lines
6.9 KiB
Plaintext
194 lines
6.9 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" />
|
|
|
|
### 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.
|
|
|
|
## 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.
|