// Which heading element a composite renders. A rule, not markup: PageHeader and Section both // need it, and a page whose headings skip a level is a screen-reader outline with holes — so the // level is a prop with one mapping, never a hardcoded

inside a component. import { invalidValueError } from '../errors'; export const HEADING_LEVELS = [1, 2, 3, 4, 5, 6] as const; export type HeadingLevel = (typeof HEADING_LEVELS)[number]; export type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; /** `2` → `'h2'`. Throws `X_UI_INVALID_VALUE` for anything off the scale. */ export function headingTag(level: HeadingLevel): HeadingTag { if (!(HEADING_LEVELS as readonly number[]).includes(level)) { throw invalidValueError('heading', level, 'a heading level from 1 to 6'); } return `h${level}` as HeadingTag; } /** * The level for content nested under a heading. Clamped at 6 rather than throwing: a deeply * nested Section is a layout the app is allowed to build, and HTML has no ``. */ export function nextHeadingLevel(level: HeadingLevel): HeadingLevel { return (level < 6 ? level + 1 : 6) as HeadingLevel; }