Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 57 additions & 3 deletions apps/design-system/content/docs/accessibility.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,68 @@ All interactive page elements should be reachable by keyboard. Given the below i

Chromium-based browsers and Firefox handle this automatically via the Tab key. Safari, by default, requires the Option key to also be held down. Enabling _Keyboard navigation_ on macOS Settings [removes this requirement](https://mayank.co/blog/safari-focus/#keyboard-navigation) but makes links non-tabbable as a result.

Interactive page elements should also provide visual feedback upon selection via a `focus-visible` state. We use consistent focus styles such as `inset-focus` so users recognize this state instantly.
Interactive page elements should also provide visual feedback upon selection via a `focus-visible` state. We use one shared focus ring so users recognize this state instantly.

[Button](components/button) has all of the above built-in. The same explicit `tabIndex` default is also baked into Checkbox, Switch, Select Trigger, Toggle, Accordion Trigger, Collapsible Trigger, Dropdown Menu Trigger, Popover Trigger, Dialog Trigger, Sheet Trigger, Alert Dialog Trigger, and the Sidebar Menu and action buttons. Bespoke interactive elements however, such as the below interactive [Table Row](components/table#examples), require these props to be added manually:
### Focus ring recipe

Prefer the shared utilities over inventing local styles:

| Utility | Use when |
| ------------- | -------------------------------------------------------------------------- |
| `focus-ring` | Buttons, inputs, and most controls (offset **ring**) |
| `focus-inset` | Dense or flush surfaces such as interactive table rows (inset **outline**) |

```tsx
className = 'focus-ring'
// or
className = 'relative cursor-pointer focus-inset'
```

These expand to:

**`focus-ring`**

```txt
outline-hidden
focus-visible:ring-2
focus-visible:ring-ring
focus-visible:ring-offset-2
focus-visible:ring-offset-background
```

**`focus-inset`**

Uses `outline` (not `ring`) so it paints reliably on interactive `<tr>`s. Tailwind `ring` is `box-shadow`, which browsers often skip on `display: table-row` (notably Safari). Do not put `focus-ring` or raw `ring-*` on a `<tr>`, and do not add `outline-hidden` alongside `focus-inset`. `outline-hidden` sets `outline-style: none` and will hide the indicator.

```txt
&:focus-visible {
outline-style: solid
outline-width: 2px
outline-offset: -2px
outline-color: var(--ring)
border-radius: var(--radius-md)
}
```

`outline-hidden` is always on (not `focus-visible:`-prefixed) so mouse click does not show the browser’s default outline; the focus indicator replaces it for keyboard focus only.

Rules:

- Prefer `:focus-visible` over `:focus` so click/tap does not show a focus indicator
- Never use `outline-none` / `outline-hidden` without a ring or outline replacement
- Always use the shared color (`ring-ring` / `outline-ring`). Variants (primary, danger, warning) do not change focus colour
- Do not animate the focus indicator; avoid `transition-all` / `transition` on controls that show one (prefer `transition-colors`)
- Prefer `focus-ring` / `focus-inset` over copy-pasting the class stack
- On interactive `<tr>`s, use `focus-inset` only. `focus-ring` will look fine in some browsers and invisible in others

When the focused element is not the thing that should show the ring (e.g. a wrapping `Link` with `group`, or an `InputGroup` parent using `:has()`), keep the explicit `group-focus-visible:ring-*` / `has-[…]:focus-visible:ring-*` stack. The utilities bake in `:focus-visible` on the same element and do not compose as `group-focus-visible:focus-ring`.

[Button](components/button) has focus, `tabIndex`, and the shared ring built-in. The same explicit `tabIndex` default is also baked into Checkbox, Switch, Select Trigger, Toggle, Accordion Trigger, Collapsible Trigger, Dropdown Menu Trigger, Popover Trigger, Dialog Trigger, Sheet Trigger, Alert Dialog Trigger, and the Sidebar Menu and action buttons. Bespoke interactive elements however, such as the below interactive [Table Row](components/table#examples), require these props to be added manually:

```tsx showLineNumbers {4-14}
<TableRow
key={id}
className="relative cursor-pointer h-16 inset-focus"
className="relative cursor-pointer h-16 focus-inset"
onClick={(event) => {
if (event.currentTarget !== event.target) return
handleBucketNavigation(bucket.id, event)
Expand Down
1 change: 1 addition & 0 deletions apps/design-system/content/docs/components/button.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,6 @@ Inside [Admonition](../fragments/admonition#split-button-with-dropdown) actions,
- 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.
2 changes: 1 addition & 1 deletion apps/design-system/content/docs/components/table.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ Avoid adding other actions when using row-level navigation, as multiple interact
When implementing row-level navigation, pay close attention to [Accessibility](/accessibility#focus-management) requirements. The row must be keyboard accessible with proper focus management. Also consider these affordances:

- Handle `Enter` and `Space` key presses for activation
- Provide visual focus indicators using classes like `inset-focus`
- Provide visual focus indicators using classes like `focus-inset`
- Support modifier keys (`Ctrl`/`Cmd`, middle-click) for opening links in new tabs
- Consider using the shared `createNavigationHandler` function to handle modifier keys
- Avoid bubbling up action events from _within_ the row
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export default function TableRowLinkActions() {
{policies.map((policy) => (
<TableRow
key={policy.id}
className="relative cursor-pointer inset-focus"
className="relative cursor-pointer focus-inset"
onClick={(event) => {
if (event.currentTarget !== event.target) return
handlePolicyNavigation(policy.id, event)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export default function TableRowLink() {
{buckets.map((bucket) => (
<TableRow
key={bucket.id}
className="relative cursor-pointer inset-focus"
className="relative cursor-pointer focus-inset"
onClick={(event) => {
if (event.currentTarget !== event.target) return
handleBucketNavigation(bucket.id, event)
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"lint:mdx": "supa-mdx-lint content --config ../../supa-mdx-lint.config.toml",
"postbuild": "pnpm run build:sitemap && ./../../scripts/upload-static-assets.sh",
"prebuild": "pnpm run codegen:graphql && pnpm run codegen:references && pnpm run codegen:examples && pnpm build:federated-content && pnpm run build:markdown && pnpm run build:gz-archive",
"predev": "pnpm run codegen:graphql && pnpm run codegen:references && pnpm run codegen:examples && pnpm build:federated-content && pnpm run build:markdown",
"predev": "pnpm run codegen:graphql && pnpm run codegen:references && pnpm run codegen:examples",
"preembeddings": "pnpm run codegen:references",
"preinstall": "npx only-allow pnpm",
"presync": "pnpm run codegen:graphql",
Expand Down Expand Up @@ -175,7 +175,7 @@
"slugify": "^1.6.6",
"smol-toml": "^1.3.1",
"tailwindcss": "catalog:",
"tar": "^7.5.13",
"tar": "^7.5.19",
"tsconfig": "workspace:*",
"tsx": "catalog:",
"twoslash": "^0.3.1",
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/scripts/generate-dart-reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ const HEADER_IDS = new Set([
'database-api',
'realtime-api',
'file-buckets',
'analytics-buckets',
'vector-buckets',
'using-modifiers',
'using-filters',
])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "analytics-buckets",
"title": "Analytics Buckets",
"notes": "This section contains methods for working with analytics buckets backed by Apache Iceberg.\n"
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"id": "oauth-server",
"title": "OAuth Server",
"notes": "Methods under the `supabase.auth.oauth` namespace are used when your Supabase project acts as an OAuth 2.1 server. They drive the user-facing consent flow and require a signed-in user. The OAuth 2.1 server feature must be enabled in your Supabase Auth configuration.\n"
"notes": "Methods under the `supabase.auth.oauth` namespace are used when your Supabase project acts as an OAuth 2.1 server. They drive the user-facing consent flow, let users manage the grants they have issued to third-party clients, and require a signed-in user. The OAuth 2.1 server feature must be enabled in your Supabase Auth configuration.\n"
}
5 changes: 5 additions & 0 deletions apps/docs/spec/reference/dart/v2/partials/vector-buckets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "vector-buckets",
"title": "Vector Buckets",
"notes": "This section contains methods for working with Vector Buckets, invoked behind the `supabase.storage.vectors` namespace.\n"
}
Loading
Loading