Files
supabase/apps/design-system/content/docs/components/button.mdx
T
Danny White 37dded67d1 feat: unify SkipToContent across studio, docs, www, and design-system (#48314)
## What kind of change does this PR introduce?

Feature / a11y polish

## What is the current behavior?

Studio and Docs each had their own skip-to-content link (different
styling and behaviour). www and design-system had none.

## What is the new behavior?

Shared `SkipToContent` in `ui-patterns`, adopted by Studio, Docs, www,
and design-system. Documented as a fragment with a short note under
Accessibility → Jumping ahead.

Tab once to reveal the button (top-left), Enter to jump to a
content-only `<main>`.

| After |
| --- |
| <img width="836" height="324" alt="CleanShot 2026-07-24 at 14 08
47@2x"
src="https://github.com/user-attachments/assets/6df29452-e53a-4eca-8f64-946f2b9f605d"
/> |

## To test

Shared steps for every app: enable Tab key navigation if needed, load
the preview, press **Tab** once — skip button should slide in top-left.
Press **Enter** — focus jumps to main content (no blue ring on
`<main>`). Press **Tab** again — first interactive control in the page
body, not the sidebar/nav. Hover the skip button — solid fill, clear
hover state, no chrome showing through.

- **Studio** —
[preview](https://studio-staging-git-dnywh-featskip-to-content-supabase.vercel.app)
→ sign in → any project page
- **Docs** —
[preview](https://docs-git-dnywh-featskip-to-content-supabase.vercel.app)
→ any docs page with sidebar
- **www** —
[preview](https://zone-www-dot-com-git-dnywh-featskip-to-content-supabase.vercel.app)
→ homepage or any marketing page with the default nav
- **Design system** —
[preview](https://design-system-git-dnywh-featskip-to-content-supabase.vercel.app)
→ any docs page (confirm Tab from content does **not** walk the
sidebar), plus [Skip to Content
fragment](https://design-system-git-dnywh-featskip-to-content-supabase.vercel.app/docs/fragments/skip-to-content)

## Additional context

Follow-up to #47694 / #48303 (Studio) and #47515 (Docs).

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

- **New Features**
- Added a reusable “Skip to content” accessibility link across key
layouts and pages.
- Updated main landmarks to support keyboard focus and skip-link
navigation (`id="main"`).
- **Accessibility**
- Skip links now follow consistent landmark-target conventions and
remain hidden until focused.
- Improved documentation for skip links/jump shortcuts in persistent
chrome layouts.
- **Documentation**
- Added a dedicated Skip to Content fragment, navigation entry, and
expanded accessibility guidance.
  - Updated button description wording in component docs.
- **Tests**
  - Added component tests for SkipToContent.
- **Chores**
  - Exposed SkipToContent via additional public package entry points.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-29 06:21:50 +10:00

150 lines
4.5 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 />
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:
- 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.