diff --git a/.github/workflows/publish_image.yml b/.github/workflows/publish_image.yml
index 40c142134ab..9865b284630 100644
--- a/.github/workflows/publish_image.yml
+++ b/.github/workflows/publish_image.yml
@@ -124,6 +124,12 @@ jobs:
docker buildx imagetools create -t supabase/studio:latest \
supabase/studio@${{ needs.release_x86.outputs.image_digest }} \
supabase/studio@${{ needs.release_arm.outputs.image_digest }}
+ echo "Published Registry Images" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "| Image | Link |" >> $GITHUB_STEP_SUMMARY
+ echo "|-------|------|" >> $GITHUB_STEP_SUMMARY
+ echo "| \`supabase/studio:${{ needs.settings.outputs.image_version }}\` | [View on Docker Hub](https://hub.docker.com/r/supabase/studio/tags?name=${{ needs.settings.outputs.image_version }}) |" >> $GITHUB_STEP_SUMMARY
+ echo "| \`supabase/studio:latest\` | [View on Docker Hub](https://hub.docker.com/r/supabase/studio/tags?name=latest) |" >> $GITHUB_STEP_SUMMARY
publish:
needs:
diff --git a/apps/design-system/content/docs/accessibility.mdx b/apps/design-system/content/docs/accessibility.mdx
index 267d8089138..8677b350c52 100644
--- a/apps/design-system/content/docs/accessibility.mdx
+++ b/apps/design-system/content/docs/accessibility.mdx
@@ -21,29 +21,88 @@ About to push some code? At a minimum, check your work against this list:
## Focus management
-All interactive page elements should be reachable by keyboard. They 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.
+All interactive page elements should be reachable by keyboard. Given the below inconsistency between devices and browsers, add `tabIndex={0}` to all buttons, links, and non-text inputs, ideally at the component level. Consider tying the state of `tabIndex` to the `disabled` state of a component, if applicable.
-```jsx
-
-
{name}
-
-
+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.
+
+[Button](../components/button) has all of the above built-in. Bespoke interactive elements however, such as the below clickable [TableRow](../components/table), require these props to be added manually:
+
+```tsx showLineNumbers {3, 5-11}
+ handleBucketNavigation(name, event)}
+ onKeyDown={(event) => {
+ if (event.key === 'Enter' || event.key === ' ') {
+ event.preventDefault()
+ handleBucketNavigation(name, event)
+ }
+ }}
+ tabIndex={0}
+>
+
+
{name}
+
+
```
Consider also affordances like `ctrl` and `meta` key support for opening in a new tab. Anything that you can do with a mouse input should be replicable by keyboard.
-## Screen reader support
+### Radio groups
-Textual elements are supported out-of-the-box by screen readers. Imagery of course should be described by `alt` tags.
+Single-select option groups such as `` should behave as a single control. Only the first item of radio groups should become focused with the Tab key. The next Tab should move focus from the group to the next focusable control.
-Less obvious however are scaffolding elements that only makes sense visually, when paired with other content. For example: a table column for actions may not have a visual _Actions_ label because its purpose is obvious to a sighted person. For everyone else’s sake, this column should be titled with `sr-only` text:
+Individual options inside of a group can be reached by arrow keys (↑ ↓ ← →). Space is the canonical key to activate radio options, with Enter being a secondary affordance.
-```jsx
+### Jumping ahead
+
+Some keyboard-navigable content may be contain hundreds or thousands of items. Help users jump to specific content with the following mitigation strategies:
+
+- Search and filtering
+- Pagination or virtualization
+- “Jump to” shortcuts to skip ahead
+
+## Screen readers
+
+Textual elements are supported out-of-the-box by screen readers.
+
+### Imagery
+
+Images should have their contents described with an `alt` attribute. Write an objective description of the content rather than its context. For example:
+
+```tsx showLineNumbers {2}
+// Correct: painting a picture with words
+
+
+// Incorrect: Unhelpful context
+
+```
+
+Icons and other visual elements that aren’t strictly images should use the `aria-label` attribute. For example:
+
+```tsx showLineNumbers {2}
+
+
+
+```
+
+Visual elements that are _purely_ visual aids may be removed from the accessibility tree via the `aria-hidden` attribute. For example:
+
+```tsx showLineNumbers {2}
+
+
+
+```
+
+Never use `aria-hidden={true}` on focusable elements, since these are critical pieces of functionality.
+
+### Scaffolding
+
+Some scaffolding elements only make sense visually, in the context of surrounding visual content. For example: a table column for actions may not have a visual _Actions_ label because its purpose is obvious (by nearby contents) to a sighted person. For everyone else’s sake, this column should be titled with `sr-only` text:
+
+```tsx showLineNumbers {2}
Actions
diff --git a/apps/design-system/content/docs/components/button.mdx b/apps/design-system/content/docs/components/button.mdx
index 53c183989c5..2fe36168c07 100644
--- a/apps/design-system/content/docs/components/button.mdx
+++ b/apps/design-system/content/docs/components/button.mdx
@@ -122,3 +122,13 @@ Displaying only an Icon in a button.
Supports slot behavior with `asChild` prop.
+
+## 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
+
+You therefore don't need to manually set `tabIndex`, as Button handles it automatically based on its `disabled` state.
diff --git a/apps/design-system/content/docs/icons.mdx b/apps/design-system/content/docs/icons.mdx
index 5e020a680d4..7dc1dbf5a67 100644
--- a/apps/design-system/content/docs/icons.mdx
+++ b/apps/design-system/content/docs/icons.mdx
@@ -49,7 +49,7 @@ function app() {
### Adding new custom icons
-To add a new custom icon to the Supabase icon library:
+Follow these steps to add a new custom icon to the Supabase icon library.
1. **Create SVG file**: Add your SVG file to `packages/icons/src/raw-icons/` with a kebab-case name (e.g., `my-new-icon.svg`). Make sure it has follows these exact requirements:
@@ -60,16 +60,18 @@ To add a new custom icon to the Supabase icon library:
- Any unnecessary elements like ``, ``, and `` wrappers have been removed
- SVG structure is as simple as possible with just `` elements
-Just leave attributes like `stroke-width` as they are. The conversion to camel-case (for React compatibility) is handled by the below build process.
+Leave attributes like `stroke-width` as they are. The conversion to camel-case for React compatibility (e.g. `strokeWidth`) is handled by the below build process.
2. **Build the component**: Run `npm run build:icons` from inside the `packages/icons` directory
3. **Use the icon**: Import and use like any other icon:
- ```jsx
- import { MyNewIcon } from 'icons'
- ;
- ```
+```tsx
+import { MyNewIcon } from 'icons'
+;
+```
+
+The icon name is automatically made available in camel-case, determined by the kebab-case input SVG. For example, `my-new-icon.svg` will become available as `MyNewIcon`.
### SVG design guidelines
diff --git a/apps/design-system/registry/default/example/empty-state-initial-state-presentational.tsx b/apps/design-system/registry/default/example/empty-state-initial-state-presentational.tsx
index 81ace8d4713..4200a1e3b4f 100644
--- a/apps/design-system/registry/default/example/empty-state-initial-state-presentational.tsx
+++ b/apps/design-system/registry/default/example/empty-state-initial-state-presentational.tsx
@@ -1,4 +1,4 @@
-import { BucketAdd } from 'icons'
+import { BucketPlus } from 'icons'
import { Plus } from 'lucide-react'
import { Button } from 'ui'
@@ -6,7 +6,7 @@ export default function EmptyStateInitialStatePresentational() {
return (