Context
Use this skill when implementing sticky headers, filters, or sidebars that need to align with a page header and be accessible to screen readers.
Patterns
1. Sticky Offset Alignment
Always match sticky top-* to exact header height.
// Header
<header className="sticky top-0 h-16">
// Sticky element
<div className="xl:sticky xl:top-16 xl:h-[calc(100vh-4rem)]">
Rule: If header is h-16 (4rem), use top-16 and h-[calc(100vh-4rem)] for viewport-filling sidebars.
Why: Mismatched offsets (e.g., top-[5.5rem] when header is h-16) create visual gaps. Using the same Tailwind unit keeps them synchronized.
2. Search Input Accessibility
Icon-only inputs must have aria-label even when placeholder exists.
<div className="relative">
<Search className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2" />
<Input
placeholder="Search…"
aria-label="Search access profiles" // Required for screen readers
className="pl-9"
/>
</div>
Why: Placeholders are not accessible labels — screen readers need aria-label.
3. Filter Button Group Accessibility
Toggle button groups need semantic roles and pressed state.
<div role="group" aria-label="Override filter">
{options.map((option) => (
<button
key={option.value}
type="button"
aria-pressed={activeFilter === option.value} // Required for toggle state
=> setActiveFilter(option.value)}
>
{option.label}
</button>
))}
</div>
Why: Screen readers need role="group" to announce the group context and aria-pressed to announce toggle state.
Examples
Access Profiles Page (src/aipolicyengine-ui/src/pages/AccessProfiles.tsx)
- Sticky client list:
xl:sticky xl:top-16 xl:h-[calc(100vh-4rem)] - Header:
h-16(Layout.tsx)
Profile Grid (src/aipolicyengine-ui/src/components/accessProfiles/ProfileGrid.tsx)
- Sticky search bar:
sticky top-16 - Search input:
aria-label="Search access profiles" - Filter group:
role="group" aria-label="Override filter"witharia-pressedon buttons
Anti-Patterns
- Using arbitrary pixel offsets like
top-[88px]instead of matching header class. - Omitting
aria-labelon icon-only search inputs. - Using plain
<div>for button groups withoutrole="group". - Toggle buttons without
aria-pressedstate.
WCAG Compliance
- 1.3.1 Info and Relationships — Button groups have semantic roles.
- 4.1.2 Name, Role, Value — Inputs have accessible names, buttons announce pressed state.