mirror of
https://github.com/supabase/supabase.git
synced 2026-09-06 09:59:03 +08:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? UI / design-system consistency (accessibility). ## What is the current behavior? Keyboard focus rings are inconsistent across Studio and `packages/ui`: - Custom Button uses thick `outline` with per-variant colours (brand / grey / destructive / warning) - Form controls use muted grey rings (`ring-background-control`) - Tabs / NavMenu / Radio use soft brand `ring-ring` - Studio `.inset-focus` uses dark green `outline-brand-600` Related: [DEPR-354](https://linear.app/supabase/issue/DEPR-354). ## What is the new behavior? One shared focus recipe, exposed as Tailwind `@utility` classes in `packages/config/css/utilities.css`: | Utility | Use when | | --- | --- | | `focus-ring` | Buttons, inputs, most controls (offset ring) | | `focus-inset` | Dense/flush surfaces such as interactive table rows (renamed from `inset-focus`) | ```txt # focus-ring outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background ``` Applied on Button, shadcn form controls, Menu/NavMenu, Command palette trigger, Studio table rows, and related call sites. Documented in the design-system accessibility docs. Variants do not change focus ring colour. When the ring must appear on a different element than the focused one (e.g. Menu + ProductMenu `Link` via `group-focus-visible`, or InputGroup via `:has()`), keep an explicit ring stack. The utilities bake in `:focus-visible` on the same element. ## Additional context **Out of scope** - Full `packages/ui` / Studio / www sweep - Legacy Studio form-group green box-shadow cleanup - ESLint rule for bare `outline-none` ## Test plan Prefer Safari (“hard mode” for `tabIndex`). Expect one soft brand ring everywhere: not grey, not solid green outline. ### Design system - [ ] [Accessibility](https://design-system-git-dnywh-choreimprove-tab-focus-styles-supabase.vercel.app/design-system/docs/accessibility): recipe docs match what you see - [ ] [Button](https://design-system-git-dnywh-choreimprove-tab-focus-styles-supabase.vercel.app/design-system/docs/components/button): Tab primary / default / danger; same ring colour - [ ] [Table → Row-level navigation](https://design-system-git-dnywh-choreimprove-tab-focus-styles-supabase.vercel.app/design-system/docs/components/table#row-level-navigation): Tab an interactive row; inset outline (`focus-inset`) sits inside the row ### Studio - [ ] **Org home → table view** (`/organizations/_` or org projects): switch to the table layout, Tab onto a project row; inset outline sits inside the row (list/card view uses CardButton, not `focus-inset`) - [ ] **Project sidebar** (Database, Auth, Storage, …): Tab the main product nav links; ring follows the focused item (not the nested section menus like Tables / Roles) - [ ] **Storage → Files**: Tab a bucket row; same inset outline as org table rows - [ ] **Project Settings → General** (or Compute and Disk): Tab through inputs, checkboxes, switches, selects; same offset ring, no ring on mouse click - [ ] **Header ⌘K** (desktop width): Tab to the search control after Feedback; same soft brand `focus-ring` (was a thicker `ring-border-strong` before) - [ ] **Table Editor or SQL Editor tabs**: focus a tab, Tab to × if active; close shows a ring - [ ] **Light + dark**: ring stays visible against both backgrounds
209 lines
5.8 KiB
CSS
209 lines
5.8 KiB
CSS
/*
|
|
* Custom Tailwind v4 utilities migrated from packages/config/ui.config.js
|
|
* (addUtilities + matchUtilities) and packages/config/tailwind.config.js
|
|
* (the .bg legacy shim).
|
|
*/
|
|
|
|
/*
|
|
* The `bg` class worked in Tailwind v3 but stopped working in v4. Restored as
|
|
* an alias for `bg-default` to avoid a wide rename across the codebase.
|
|
* Should be deprecated in favor of `bg-default`.
|
|
*/
|
|
@utility bg {
|
|
background-color: var(--background);
|
|
}
|
|
|
|
/*
|
|
* Soft shimmer gradient used by LoadingLine.
|
|
*/
|
|
@utility line-loading-bg {
|
|
background: rgb(0, 0, 0);
|
|
background: linear-gradient(
|
|
90deg,
|
|
rgba(0, 0, 0, 0) 0%,
|
|
rgba(255, 255, 255, 0.65) 50%,
|
|
rgba(0, 0, 0, 0) 100%
|
|
);
|
|
}
|
|
@utility line-loading-bg-light {
|
|
background: rgb(0, 0, 0);
|
|
background: linear-gradient(
|
|
90deg,
|
|
rgba(0, 0, 0, 0) 0%,
|
|
rgba(33, 33, 33, 0.65) 50%,
|
|
rgba(0, 0, 0, 0) 100%
|
|
);
|
|
}
|
|
|
|
/* may be unused — verify before pruning */
|
|
@utility mask-fadeout-right {
|
|
-webkit-mask-image: linear-gradient(to right, white 98%, transparent 100%);
|
|
mask-image: linear-gradient(to right, white 98%, transparent 100%);
|
|
}
|
|
/* may be unused — verify before pruning */
|
|
@utility mask-fadeout-left {
|
|
-webkit-mask-image: linear-gradient(to left, white 98%, transparent 100%);
|
|
mask-image: linear-gradient(to left, white 98%, transparent 100%);
|
|
}
|
|
|
|
/*
|
|
* Functional color utilities. The `--value(--color-*)` token accepts any
|
|
* registered --color-* design token (e.g. highlight-foreground-light).
|
|
*/
|
|
/* may be unused — verify before pruning */
|
|
@utility highlight-* {
|
|
box-shadow: inset 0 1px 0 0 --value(--color-*);
|
|
}
|
|
/* may be unused — verify before pruning */
|
|
@utility subhighlight-* {
|
|
box-shadow: inset 0 -1px 0 0 --value(--color-*);
|
|
}
|
|
|
|
/*
|
|
* Inline-SVG grid background. Used by ProjectLinker as bg-grid-black/5 and
|
|
* bg-grid-white/5 for the integrations onboarding visuals.
|
|
*/
|
|
@utility bg-grid-* {
|
|
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' width='32' height='32' fill='none' stroke='--value(--color-*)'><path d='M0 .5H31.5V32'/></svg>");
|
|
}
|
|
|
|
@utility no-scrollbar {
|
|
-ms-overflow-style: none; /* IE and Edge */
|
|
scrollbar-width: none; /* Firefox */
|
|
&::-webkit-scrollbar {
|
|
display: none; /* Chrome, Safari, Opera */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Text shimmer (shadcn shimmer utility, inlined so we don't pull in the full
|
|
* shadcn CSS package). Apply `shimmer` to text to draw attention.
|
|
*/
|
|
@property --shimmer-angle {
|
|
syntax: '<angle>';
|
|
inherits: true;
|
|
initial-value: 20deg;
|
|
}
|
|
@property --shimmer-image {
|
|
syntax: '*';
|
|
inherits: false;
|
|
}
|
|
@property --shimmer-text-fill {
|
|
syntax: '*';
|
|
inherits: false;
|
|
}
|
|
|
|
@utility shimmer {
|
|
--_spread: var(--shimmer-spread, calc(3ch + 40px));
|
|
--_base: currentColor;
|
|
--_highlight: var(--shimmer-color, oklch(from currentColor l c h / calc(alpha* 0.2)));
|
|
|
|
background-image: var(
|
|
--shimmer-image,
|
|
linear-gradient(
|
|
calc(90deg + var(--shimmer-angle)),
|
|
var(--_base) calc(50% - var(--_spread)),
|
|
color-mix(in oklch, var(--_highlight), var(--_base) 50%) calc(50% - var(--_spread) * 0.5),
|
|
var(--_highlight) 50%,
|
|
color-mix(in oklch, var(--_highlight), var(--_base) 50%) calc(50% + var(--_spread) * 0.5),
|
|
var(--_base) calc(50% + var(--_spread))
|
|
)
|
|
);
|
|
background-repeat: no-repeat;
|
|
background-size: calc(200% + var(--_spread) * 2) 100%;
|
|
background-position: 0 0;
|
|
background-clip: text;
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: var(--shimmer-text-fill, transparent);
|
|
animation: tw-shimmer var(--shimmer-duration, 2s) linear infinite;
|
|
transition: -webkit-text-fill-color 0.5s ease;
|
|
|
|
/* Fade to solid text on dismiss — keep the gradient until fill is opaque */
|
|
&[data-shimmer-fading] {
|
|
-webkit-text-fill-color: currentColor;
|
|
animation-play-state: paused;
|
|
}
|
|
|
|
@variant dark {
|
|
--_highlight: var(
|
|
--shimmer-color,
|
|
oklch(from currentColor max(0.8, calc(l + 0.4)) c h / calc(alpha + 0.4))
|
|
);
|
|
}
|
|
|
|
&:where([dir='rtl'], [dir='rtl'] *) {
|
|
animation-direction: reverse;
|
|
}
|
|
}
|
|
|
|
@utility shimmer-once {
|
|
animation-iteration-count: 1;
|
|
}
|
|
|
|
@utility shimmer-reverse {
|
|
animation-direction: reverse;
|
|
}
|
|
|
|
@utility shimmer-none {
|
|
--shimmer-image: none;
|
|
--shimmer-text-fill: currentColor;
|
|
background-image: none;
|
|
-webkit-text-fill-color: currentColor;
|
|
}
|
|
|
|
@utility shimmer-color-* {
|
|
--shimmer-color: --value(--color-*, [color]);
|
|
--shimmer-color: color-mix(
|
|
in oklch,
|
|
--value(--color-*, [color]) calc(--modifier(integer) * 1%),
|
|
transparent
|
|
);
|
|
}
|
|
|
|
@utility shimmer-duration-* {
|
|
--shimmer-duration: calc(--value(integer) * 1ms);
|
|
}
|
|
|
|
@utility shimmer-spread-* {
|
|
--shimmer-spread: calc(var(--spacing) * --value(integer));
|
|
--shimmer-spread: --value([length], [percentage]);
|
|
}
|
|
|
|
@utility shimmer-angle-* {
|
|
--shimmer-angle: calc(--value(integer) * 1deg);
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.shimmer {
|
|
animation: none;
|
|
background-image: none;
|
|
-webkit-text-fill-color: currentColor;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Canonical keyboard focus rings. Prefer these over inventing local ring/outline
|
|
* stacks.
|
|
*
|
|
* - focus-ring: default offset ring (buttons, inputs, most controls).
|
|
* `outline-hidden` stays always-on so mouse click does not show the browser
|
|
* default; the ring is `:focus-visible` only.
|
|
* - focus-inset: dense/flush surfaces such as interactive <tr>s. Must use
|
|
* outline (not ring/box-shadow) — box-shadow on table-row is unreliable,
|
|
* especially in Safari. Do NOT pair with outline-hidden: that sets
|
|
* outline-style: none and suppresses the focus outline.
|
|
*/
|
|
@utility focus-ring {
|
|
@apply outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background;
|
|
}
|
|
|
|
@utility focus-inset {
|
|
&:focus-visible {
|
|
outline-style: solid;
|
|
outline-width: 2px;
|
|
outline-offset: -2px;
|
|
outline-color: var(--ring);
|
|
border-radius: var(--radius-md, 0.375rem);
|
|
}
|
|
}
|