ARIA State
Overview
ARIA state modifiers let you apply utility styles conditionally based on ARIA attribute values. They wrap utility declarations in attribute selectors like [aria-expanded="true"], generating variant utility classes that respond to accessibility state changes.
Why Use ARIA State Modifiers?
ARIA state modifiers help you:
- Style accessible components: Apply styles based on ARIA attributes used by screen readers
- Avoid JavaScript class toggling: Let ARIA attributes drive both accessibility and visual presentation
- Build inclusive UIs: Keep visual state in sync with the accessibility tree
- Follow WAI-ARIA patterns: Style disclosure widgets, tabs, accordions, and more using standard ARIA attributes
useAriaBusyModifier
The useAriaBusyModifier() function creates a modifier that applies styles when aria-busy="true".
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
aria-busy | &[aria-busy="true"] |
useAriaCheckedModifier
The useAriaCheckedModifier() function creates a modifier that applies styles when aria-checked="true".
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
aria-checked | &[aria-checked="true"] |
useAriaDisabledModifier
The useAriaDisabledModifier() function creates a modifier that applies styles when aria-disabled="true".
import { styleframe } from "styleframe";
import { useOpacityUtility, useCursorUtility } from "@styleframe/theme";
import { useAriaDisabledModifier } from "@styleframe/theme";
const s = styleframe();
const ariaDisabled = useAriaDisabledModifier(s);
useOpacityUtility(s, { 50: '0.5' }, [ariaDisabled]);
useCursorUtility(s, { 'not-allowed': 'not-allowed' }, [ariaDisabled]);
export default s;
._opacity\:50 { opacity: 0.5; }
._cursor\:not-allowed { cursor: not-allowed; }
._aria-disabled\:opacity\:50 {
&[aria-disabled="true"] { opacity: 0.5; }
}
._aria-disabled\:cursor\:not-allowed {
&[aria-disabled="true"] { cursor: not-allowed; }
}
<!-- Style custom disabled button -->
<div
role="button"
aria-disabled="true"
class="_aria-disabled:opacity:50 _aria-disabled:cursor:not-allowed"
>
Disabled action
</div>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
aria-disabled | &[aria-disabled="true"] |
useAriaExpandedModifier
The useAriaExpandedModifier() function creates a modifier that applies styles when aria-expanded="true".
import { styleframe } from "styleframe";
import { useDisplayUtility, useRotateUtility } from "@styleframe/theme";
import { useAriaExpandedModifier } from "@styleframe/theme";
const s = styleframe();
const ariaExpanded = useAriaExpandedModifier(s);
useDisplayUtility(s, {
block: 'block',
none: 'none',
}, [ariaExpanded]);
useRotateUtility(s, {
180: '180deg',
}, [ariaExpanded]);
export default s;
._display\:block { display: block; }
._display\:none { display: none; }
._rotate\:180 { rotate: 180deg; }
._aria-expanded\:display\:block {
&[aria-expanded="true"] { display: block; }
}
._aria-expanded\:rotate\:180 {
&[aria-expanded="true"] { rotate: 180deg; }
}
<!-- Accordion trigger with rotating icon -->
<button aria-expanded="false" class="_aria-expanded:rotate:180">
<span>Toggle section</span>
<svg class="icon"><!-- chevron icon --></svg>
</button>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
aria-expanded | &[aria-expanded="true"] |
useAriaHiddenModifier
The useAriaHiddenModifier() function creates a modifier that applies styles when aria-hidden="true".
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
aria-hidden | &[aria-hidden="true"] |
useAriaPressedModifier
The useAriaPressedModifier() function creates a modifier that applies styles when aria-pressed="true".
import { styleframe } from "styleframe";
import { useColor } from "@styleframe/theme";
import { useBackgroundColorUtility } from "@styleframe/theme";
import { useAriaPressedModifier } from "@styleframe/theme";
const s = styleframe();
const { ref } = s;
const { colorPrimary } = useColor(s, { primary: '#006cff' } as const);
const ariaPressed = useAriaPressedModifier(s);
useBackgroundColorUtility(s, {
primary: ref(colorPrimary),
muted: '#e9ecef',
}, [ariaPressed]);
export default s;
._background-color\:primary { background-color: var(--color--primary); }
._background-color\:muted { background-color: #e9ecef; }
._aria-pressed\:background-color\:primary {
&[aria-pressed="true"] { background-color: var(--color--primary); }
}
<!-- Toggle button -->
<button
role="button"
aria-pressed="false"
class="_background-color:muted _aria-pressed:background-color:primary"
>
Toggle
</button>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
aria-pressed | &[aria-pressed="true"] |
useAriaReadonlyModifier
The useAriaReadonlyModifier() function creates a modifier that applies styles when aria-readonly="true".
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
aria-readonly | &[aria-readonly="true"] |
useAriaRequiredModifier
The useAriaRequiredModifier() function creates a modifier that applies styles when aria-required="true".
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
aria-required | &[aria-required="true"] |
useAriaSelectedModifier
The useAriaSelectedModifier() function creates a modifier that applies styles when aria-selected="true".
import { styleframe } from "styleframe";
import { useColor } from "@styleframe/theme";
import { useBackgroundColorUtility, useBorderColorUtility } from "@styleframe/theme";
import { useAriaSelectedModifier } from "@styleframe/theme";
const s = styleframe();
const { ref } = s;
const { colorPrimary } = useColor(s, { primary: '#006cff' } as const);
const ariaSelected = useAriaSelectedModifier(s);
useBackgroundColorUtility(s, {
primary: ref(colorPrimary),
white: '#ffffff',
}, [ariaSelected]);
useBorderColorUtility(s, {
primary: ref(colorPrimary),
}, [ariaSelected]);
export default s;
._aria-selected\:background-color\:primary {
&[aria-selected="true"] { background-color: var(--color--primary); }
}
._aria-selected\:border-color\:primary {
&[aria-selected="true"] { border-color: var(--color--primary); }
}
<!-- Tab navigation -->
<div role="tablist">
<button role="tab" aria-selected="true"
class="_background-color:white _aria-selected:background-color:primary">
Tab 1
</button>
<button role="tab" aria-selected="false"
class="_background-color:white _aria-selected:background-color:primary">
Tab 2
</button>
</div>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
aria-selected | &[aria-selected="true"] |
useAriaStateModifiers
The useAriaStateModifiers() function registers all ARIA state modifiers at once and returns them as a destructurable object.
Returned Modifiers
| Key | Modifier Name | CSS Selector |
|---|---|---|
ariaBusy | aria-busy | &[aria-busy="true"] |
ariaChecked | aria-checked | &[aria-checked="true"] |
ariaDisabled | aria-disabled | &[aria-disabled="true"] |
ariaExpanded | aria-expanded | &[aria-expanded="true"] |
ariaHidden | aria-hidden | &[aria-hidden="true"] |
ariaPressed | aria-pressed | &[aria-pressed="true"] |
ariaReadonly | aria-readonly | &[aria-readonly="true"] |
ariaRequired | aria-required | &[aria-required="true"] |
ariaSelected | aria-selected | &[aria-selected="true"] |
Examples
Accessible Accordion
import { styleframe } from "styleframe";
import { useDisplayUtility, useRotateUtility, useOpacityUtility } from "@styleframe/theme";
import { useAriaStateModifiers } from "@styleframe/theme";
const s = styleframe();
const { ariaExpanded, ariaHidden } = useAriaStateModifiers(s);
useDisplayUtility(s, {
block: 'block',
none: 'none',
}, [ariaExpanded, ariaHidden]);
useRotateUtility(s, {
180: '180deg',
}, [ariaExpanded]);
export default s;
<div class="accordion">
<button aria-expanded="true" class="_aria-expanded:rotate:180">
Section 1 <span class="chevron">▼</span>
</button>
<div aria-hidden="false">
<p>Accordion content here.</p>
</div>
</div>
Tab Panel with Selected State
import { styleframe } from "styleframe";
import { useColor } from "@styleframe/theme";
import {
useBackgroundColorUtility,
useTextColorUtility,
useBorderColorUtility,
} from "@styleframe/theme";
import { useAriaSelectedModifier } from "@styleframe/theme";
const s = styleframe();
const { ref } = s;
const { colorPrimary } = useColor(s, { primary: '#006cff' } as const);
const ariaSelected = useAriaSelectedModifier(s);
useBackgroundColorUtility(s, {
primary: ref(colorPrimary),
transparent: 'transparent',
}, [ariaSelected]);
useTextColorUtility(s, {
white: '#ffffff',
dark: '#212529',
}, [ariaSelected]);
useBorderColorUtility(s, {
primary: ref(colorPrimary),
transparent: 'transparent',
}, [ariaSelected]);
export default s;
<div role="tablist">
<button
role="tab"
aria-selected="true"
class="_background-color:transparent _aria-selected:background-color:primary _text-color:dark _aria-selected:text-color:white"
>
Active Tab
</button>
<button
role="tab"
aria-selected="false"
class="_background-color:transparent _aria-selected:background-color:primary _text-color:dark _aria-selected:text-color:white"
>
Inactive Tab
</button>
</div>
Best Practices
- Use ARIA modifiers for custom components: Native form elements have built-in pseudo-classes; use ARIA modifiers for custom widgets
- Keep ARIA attributes in sync: Ensure JavaScript updates ARIA attributes so styles reflect the current state
- Prefer semantic HTML: When possible, use native elements (like
<details>) that have built-in ARIA support - Test with screen readers: Verify that ARIA attributes are correctly announced alongside the visual changes
- Use aria-expanded for disclosure widgets: Accordions, dropdowns, and menus should use
aria-expandedfor both accessibility and styling
FAQ
:disabled, :checked) for native form elements like <input>, <select>, and <button>. Use ARIA state modifiers for custom components built with <div>, <span>, or other non-form elements that need aria-* attributes for accessibility.[aria-*="true"]) that work with any HTML element. The selector matches as long as the element has the corresponding ARIA attribute set to "true".[ariaExpanded, hover] generates _aria-expanded:property:value, _hover:property:value, and combined variants for nuanced interactive styling.