mirror of
https://github.com/supabase/supabase.git
synced 2026-09-22 13:37:53 +08:00
9f5b5ea6a7
## What kind of change does this PR introduce? Bug fix and design-system accessibility update. ## What is the current behavior? Accordion and collapsible triggers do not consistently use the shared focus treatment. Accordion focus can animate from the browser outline, while call sites need to manage radius and focus geometry themselves. ## What is the new behavior? Bare accordion and collapsible triggers use the rounded `focus-inset` treatment by default, positioned 2px inside the control. Interactive children composed with `asChild`, such as a standard `Button`, retain their own focus treatment. Accordion triggers include a small default horizontal hit-area padding (`px-2`) and retain an explicit outer-ring option for standalone controls. The focus indicator is not animated. Focused tests cover the default and opt-out accordion variants. | Before | After | | --- | --- | | <img width="1792" height="384" alt="CleanShot 2026-09-07 at 15 40 23@2x" src="https://github.com/user-attachments/assets/8c8fd99a-3669-465a-a7b2-d272ec92dec7" /> | <img width="1784" height="498" alt="CleanShot 2026-09-07 at 15 39 52@2x" src="https://github.com/user-attachments/assets/7a0b7de1-a4bc-48cf-b9fc-3421618806b9" /> | | <img width="1564" height="360" alt="CleanShot 2026-09-07 at 15 35 11@2x" src="https://github.com/user-attachments/assets/34945180-e671-4dc2-981a-4c683d08f221" /> | <img width="1552" height="378" alt="CleanShot 2026-09-07 at 15 36 19@2x" src="https://github.com/user-attachments/assets/09f2c030-9ffb-4fa5-bf63-5e172326e8ef" /> | ## Visual impact Most call sites should look the same at rest. The exceptions are accordion triggers that did not already set their own horizontal padding: they pick up the new default `px-2`, `rounded-md`, and lose the old blanket `transition-all` on the trigger (chevron rotation is unchanged). | Resting layout change | Why | | --- | --- | | Accordion triggers without an explicit `px-*` | Inherit new default `px-2` and `rounded-md` | | Accordion triggers with their own `px-*` | Unchanged (call-site padding wins) | | Collapsible triggers | Focus ring only; spacing comes from the child element | | Direct `focus-inset` users (table rows, etc.) | Focus ring only | ## To test Use Tab to focus each disclosure trigger. Confirm the focus outline is rounded, sits 2px inside the trigger, and appears immediately without a dark flash. Previews: [design-system](https://design-system-git-dnywh-fixaccordion-focus-rings-supabase.vercel.app) · [studio-staging](https://studio-staging-git-dnywh-fixaccordion-focus-rings-supabase.vercel.app) · [docs](https://docs-git-dnywh-fixaccordion-focus-rings-supabase.vercel.app) · [www](https://zone-www-dot-com-git-dnywh-fixaccordion-focus-rings-supabase.vercel.app) ### Design system - [Accordion](https://design-system-git-dnywh-fixaccordion-focus-rings-supabase.vercel.app/design-system/docs/components/accordion) - [Collapsible](https://design-system-git-dnywh-fixaccordion-focus-rings-supabase.vercel.app/design-system/docs/components/collapsible) ### Studio (focus-only unless noted) - [Account → Access tokens → **New token**](https://studio-staging-git-dnywh-fixaccordion-focus-rings-supabase.vercel.app/dashboard/account/tokens): permission category accordions (already `px-4`) - [Database → Roles](https://studio-staging-git-dnywh-fixaccordion-focus-rings-supabase.vercel.app/dashboard/project/_/database/roles): role row collapsibles - [Observability → Query performance](https://studio-staging-git-dnywh-fixaccordion-focus-rings-supabase.vercel.app/dashboard/project/_/observability/query-performance): index suggestion accordions (already `px-4`) - [Settings → JWT (legacy)](https://studio-staging-git-dnywh-fixaccordion-focus-rings-supabase.vercel.app/dashboard/project/_/settings/jwt/legacy): migration help collapsible ### Docs - Docs mobile nav (narrow viewport): accordion sections **(resting layout: +px-2)** ### WWW - [Pricing → FAQs](https://zone-www-dot-com-git-dnywh-fixaccordion-focus-rings-supabase.vercel.app/pricing): FAQ accordions **(resting layout: +px-2)** - WWW mobile nav (narrow viewport): menu accordions **(resting layout: +px-2)** ### Not worth opening manually Sheets, modals, error fallbacks, and replication/cron/integration advanced-settings accordions inherit the focus fix but need specific flows or failure states to reach. Error-state troubleshooting accordions (`TroubleshootingSections`) also gain **+px-2** when visible. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## New Features - Accordion triggers now use an inset focus style by default, with an option to use the standard ring style. - Accordion and collapsible trigger styling can be customized more flexibly. ## Accessibility - Focus indicators appear consistently without flashing browser defaults. - Focus transitions are limited to visual properties that preserve immediate keyboard focus visibility. ## Documentation - Updated accessibility guidance explains inset focus behavior and transition considerations. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
214 lines
6.1 KiB
CSS
214 lines
6.1 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 {
|
|
/* Reserve outline geometry up front so focus never flashes the browser default. */
|
|
outline: 2px solid transparent;
|
|
outline-offset: -2px;
|
|
/* Call sites often add `transition`/`transition-all`; keep outline instant. */
|
|
transition-property:
|
|
color, background-color, border-color, text-decoration-color, fill, stroke, opacity, transform,
|
|
filter, backdrop-filter, box-shadow;
|
|
|
|
&:focus-visible {
|
|
outline-color: var(--ring);
|
|
border-radius: var(--radius-md, 0.375rem);
|
|
}
|
|
}
|