fix(docs): button a11y enhancements (#49952)

## What kind of change does this PR introduce?

bug fix for accessibility, fixes
[docs-1265](https://linear.app/supabase/issue/DOCS-1365/add-aria-label-to-icon-only-buttons-button-name)

## What is the current behavior?

some controles in the docs app have no accessible name, so screen
readers announce them as an unlabelled "button"

## What is the new behavior?

- adds each control a name the way its neighbours already do
- adds menu toggle a state-dependent `aria-label` and `aria-expanded`
- adds `button-name` to `enforced_rules`

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

- **Accessibility**
- Added descriptive labels to configuration selectors and mobile menu
controls.
- Mobile menu controls now announce whether the menu is open or closed.
- Expanded accessibility validation to check button names across
documentation pages.

- **UI Improvements**
- Updated the raw throughput table control with a clearer button
appearance and expandable chevron indicator.
  - Replaced a schema-migration tooltip with a direct in-page link.

- **Documentation**
- Removed the Info Tooltip component guidance from the contributing
documentation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Anthony Lio authored and GitHub committed 2026-09-04 10:32:57 +03:00
1 parent c1df3711a6
commit 085d218bff
8 files changed
+18 -157

No files matched your search

-8
View File
@@ -472,14 +472,6 @@ void main() async {
</TabPanel>
</Tabs>
### Info Tooltip
The <InfoTooltip tooltipContent="This is an info tooltip">InfoTooltip</InfoTooltip> component is used to add more context to a word or phrase via tooltip.
```mdx
<InfoTooltip tooltipContent="The Postgres Development Platform.">Supabase</InfoTooltip>
```
## Partials
We incorporate content reuse in the docs to avoid duplication. If you find yourself writing the same content over and over, you can put it in a partial instead. Here are some examples of commonly used partials:
@@ -86,6 +86,7 @@ export function ComboBox<Opt extends ComboBoxOption>({
role="combobox"
disabled={disabled}
aria-expanded={open}
aria-label={`Select your ${name}`}
className={cn(
'overflow-hidden',
'h-auto min-h-10',
@@ -2,7 +2,7 @@ import throughputTable from '~/data/realtime/throughput.json'
import { ChevronDown } from 'lucide-react'
import { useState } from 'react'
import {
Button,
cn,
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
@@ -138,22 +138,17 @@ export default function RealtimeLimitsEstimater({}) {
)}
<Collapsible open={expandPreview} onOpenChange={setExpandPreview}>
<CollapsibleTrigger asChild>
<div className="py-1 flex items-center">
<p className="text-sm">View raw throughput table</p>
<Button
variant="text"
icon={
<ChevronDown
size={18}
strokeWidth={2}
className={expandPreview ? 'rotate-180' : undefined}
/>
}
className="px-1"
onClick={() => setExpandPreview(!expandPreview)}
/>
</div>
<CollapsibleTrigger className="group text-base py-1 flex items-center gap-1 cursor-pointer select-none mb-10 text-foreground-light hover:text-foreground transition-colors">
View raw throughput table
<ChevronDown
size={14}
strokeWidth={2}
aria-hidden
className={cn(
'text-foreground-lighter group-hover:text-foreground transition-colors',
expandPreview && 'rotate-180'
)}
/>
</CollapsibleTrigger>
<CollapsibleContent>
<div>
@@ -7,7 +7,7 @@ subtitle: 'Manage your database schemas in one place and generate versioned migr
## Overview
Declarative schemas provide a developer-friendly way to maintain <InfoTooltip tooltipContent={<><p>Files of SQL statements that track the evolution of your database schema over time.<br />They allow you to version control your database schema alongside your application code.</p><p>See the <Link href="/guides/deployment/database-migrations" className="underline">database migrations</Link> guide to learn more.</p></>}>schema migrations</InfoTooltip>.
Declarative schemas provide a developer-friendly way to maintain [schema migrations](#schema-migrations).
[Migrations](/docs/guides/deployment/database-migrations) are traditionally managed imperatively (you provide the instructions on how exactly to change the database). This can lead to related information being scattered over multiple migration files. With declarative schemas, you instead declare the state you want your database to be in, and the instructions are generated for you.
@@ -27,7 +27,6 @@ import { NamedCodeBlock } from '~/features/directives/CodeTabs.components'
import { MdxAnchor } from '~/features/docs/MdxAnchor'
import { Accordion, AccordionItem } from '~/features/ui/Accordion'
import { CodeBlock } from '~/features/ui/CodeBlock/CodeBlock'
import InfoTooltip from '~/features/ui/InfoTooltip'
import { ShowUntil } from '~/features/ui/ShowUntil'
import { TabPanel, Tabs } from '~/features/ui/Tabs'
import { ArrowDown, Check, X } from 'lucide-react'
@@ -114,7 +113,6 @@ const components = {
TabPanel,
TerraformProviderSchema,
WrapperDashboardIntegration,
InfoTooltip,
a: MdxAnchor,
h2: (props: ComponentPropsWithoutRef<'h2'>) => (
<Heading tag="h2" {...props}>
-127
View File
@@ -1,127 +0,0 @@
'use client'
import { useBreakpoint } from 'common'
import { InfoIcon, XIcon } from 'lucide-react'
import React, {
useCallback,
useEffect,
useId,
useRef,
useState,
type PropsWithChildren,
} from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import {
Button,
cn,
CommandEmpty,
Sheet,
SheetContent,
SheetHeader,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
interface PopUpProps extends PropsWithChildren {
tooltipContent: React.ReactNode
className?: string
contentContainerClassName?: string
}
const buttonClassName = cn(
'relative px-1 py-0 -my-px',
'rounded-sm bg-surface-200 border border-dashed',
'transition-colors hover:border-strong group/inline-popup'
)
const InfoTooltip = ({
children,
className,
tooltipContent,
contentContainerClassName,
}: PopUpProps) => {
const id = useId().replaceAll(':', '')
const timeout = useRef<ReturnType<typeof setTimeout> | null>(null)
const [mobileSheetOpen, setMobileSheetOpen] = useState(false)
const [tooltipOpen, _setTooltipOpen] = useState(false)
const isMobile = useBreakpoint('md')
const setTooltipOpen = useCallback(
(open: boolean) => {
_setTooltipOpen(open)
setMobileSheetOpen(true)
timeout.current = setTimeout(() => {
if (isMobile) return
const targetElem: HTMLElement | null = document.querySelector(`#tooltip-content-${id}`)
targetElem?.focus()
})
},
[_setTooltipOpen, id, isMobile]
)
useEffect(() => {
return () => {
if (timeout.current) {
clearTimeout(timeout.current)
}
}
}, [])
return (
<>
<Tooltip open={tooltipOpen} onOpenChange={(open) => !isMobile && setTooltipOpen(open)}>
<TooltipTrigger asChild>
<span
role="button"
tabIndex={0}
onClick={() => setMobileSheetOpen(true)}
className={cn(buttonClassName, className)}
>
{children}
<InfoIcon
aria-hidden={true}
className="absolute p-px bg-background rounded-full -left-1.5 -top-1.5 w-3 h-3 text-foreground-lighter group-hover/inline-popup:text-foreground-light transition-colors"
/>
</span>
</TooltipTrigger>
<TooltipContent id={`tooltip-content-${id}`} className={contentContainerClassName}>
{tooltipContent}
</TooltipContent>
</Tooltip>
{isMobile && (
<Sheet open={mobileSheetOpen} onOpenChange={setMobileSheetOpen}>
<SheetContent
id={`mobile-sheet-content-${id}`}
showClose={false}
size="full"
side="bottom"
className={cn(
'md:hidden',
'text-foreground-lighter',
'rounded-t-lg overflow-hidden overflow-y-scroll',
'w-full h-fit min-h-[200px] py-2 px-4'
)}
>
<ErrorBoundary FallbackComponent={() => <CommandEmpty />}>
<SheetHeader className="flex items-center justify-between gap-2 px-0 py-2 mb-2 max-w-full">
<div className="flex items-center gap-2 max-w-[90%]">
<InfoIcon className="p-px min-w-4 min-h-4 text-foreground-lighter" />
<p className="italic text-foreground-light truncate">{children}</p>
</div>
<Button variant="text" onClick={() => setMobileSheetOpen(false)} className="px-1">
<XIcon className="w-4 h-4 text-foreground-lighter" />
</Button>
</SheetHeader>
{tooltipContent}
</ErrorBoundary>
</SheetContent>
</Sheet>
)}
</>
)
}
export default InfoTooltip
+2
View File
@@ -214,6 +214,8 @@ const MobileHeader = memo(function MobileHeader(props: MobileHeaderProps) {
mobileMenuOpen && 'mt-0.5'
)}
onClick={() => menuState.setMenuMobileOpen(!mobileMenuOpen)}
aria-label={mobileMenuOpen ? 'Close menu' : 'Open menu'}
aria-expanded={mobileMenuOpen}
>
<div
className={cn(
+2 -2
View File
@@ -5,7 +5,7 @@ import { scan } from '../../shared/axe.ts'
export const WCAG_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']
export const ENFORCED_RULES = ['heading-order', 'page-has-heading-one']
export const ENFORCED_RULES = ['heading-order', 'page-has-heading-one', 'button-name']
export const EXCLUDED_RULES = [
'color-contrast',
@@ -40,7 +40,7 @@ export async function scanArticle(
include: string
): Promise<A11yScanResult> {
const reported = await scan(page, { tags: WCAG_TAGS, excludeRules: EXCLUDED_RULES, include })
const enforced = await scan(page, { rules: ENFORCED_RULES, include })
const enforced = await scan(page, { rules: ENFORCED_RULES })
const byRule = new Map([...reported, ...enforced].map((violation) => [violation.id, violation]))