Name labeled toggle controls

This commit is contained in:
rcourtman
2026-05-20 14:13:38 +01:00
parent f83d25d155
commit b8d23ae616
2 changed files with 31 additions and 8 deletions
@@ -51,4 +51,15 @@ describe('Toggle', () => {
expect(screen.getByText('Enabled')).toBeInTheDocument();
expect(screen.getByText('Turns it on')).toBeInTheDocument();
});
it('uses the visible label and description for the primitive button semantics', () => {
render(() => (
<Toggle checked={true} label={<span>Enabled</span>} description={<span>Turns it on</span>} />
));
const button = screen.getByRole('button', { name: 'Enabled' });
expect(button).toHaveAttribute('aria-labelledby');
expect(button).toHaveAttribute('aria-describedby');
expect(button).toHaveAccessibleDescription('Turns it on');
});
});
@@ -1,4 +1,4 @@
import { JSX } from 'solid-js';
import { JSX, createUniqueId } from 'solid-js';
import {
getToggleContainerClass,
getToggleDescriptionClass,
@@ -9,16 +9,15 @@ import {
type ToggleSize,
} from '@/components/shared/toggleModel';
export type { ToggleChangeEvent } from '@/components/shared/toggleModel';
import {
type ToggleRuntimeProps,
useToggleState,
} from '@/components/shared/useToggleState';
import { type ToggleRuntimeProps, useToggleState } from '@/components/shared/useToggleState';
interface BaseToggleProps extends ToggleRuntimeProps {
size?: ToggleSize;
class?: string;
title?: string;
ariaLabel?: string;
ariaLabelledBy?: string;
ariaDescribedBy?: string;
}
export function TogglePrimitive(props: BaseToggleProps): JSX.Element {
@@ -34,6 +33,8 @@ export function TogglePrimitive(props: BaseToggleProps): JSX.Element {
title={props.title}
aria-pressed={props.checked ? 'true' : 'false'}
aria-label={props.ariaLabel}
aria-labelledby={props.ariaLabel ? undefined : props.ariaLabelledBy}
aria-describedby={props.ariaDescribedBy}
>
<span class={getToggleKnobClass(size, props.checked, state.isDisabled())} />
</button>
@@ -50,14 +51,25 @@ interface LabeledToggleProps extends BaseToggleProps {
export function Toggle(props: LabeledToggleProps) {
const size = resolveToggleSize(props.size, 'md');
const labelId = createUniqueId();
const descriptionId = createUniqueId();
return (
<div class={getToggleContainerClass(props.containerClass)}>
<TogglePrimitive {...props} size={size} />
<TogglePrimitive
{...props}
size={size}
ariaLabelledBy={props.label ? labelId : undefined}
ariaDescribedBy={props.description ? descriptionId : undefined}
/>
{(props.label || props.description) && (
<span class={getToggleLabelClass()}>
{props.label}
{props.description && <span class={getToggleDescriptionClass()}>{props.description}</span>}
{props.label && <span id={labelId}>{props.label}</span>}
{props.description && (
<span id={descriptionId} class={getToggleDescriptionClass()}>
{props.description}
</span>
)}
</span>
)}
</div>