{"id":22403315,"url":"https://github.com/springload/react-accessible-headings","last_synced_at":"2025-07-31T16:31:42.869Z","repository":{"id":41978282,"uuid":"236226196","full_name":"springload/react-accessible-headings","owner":"springload","description":"Makes it easier to keep heading levels semantic and accessible (WCAG)","archived":false,"fork":false,"pushed_at":"2022-04-20T21:05:18.000Z","size":499,"stargazers_count":35,"open_issues_count":2,"forks_count":3,"subscribers_count":13,"default_branch":"master","last_synced_at":"2024-11-14T10:30:43.019Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/react-accessible-headings","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/springload.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-01-25T20:33:09.000Z","updated_at":"2024-08-16T00:47:33.000Z","dependencies_parsed_at":"2022-08-12T01:20:11.178Z","dependency_job_id":null,"html_url":"https://github.com/springload/react-accessible-headings","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/springload%2Freact-accessible-headings","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/springload%2Freact-accessible-headings/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/springload%2Freact-accessible-headings/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/springload%2Freact-accessible-headings/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/springload","download_url":"https://codeload.github.com/springload/react-accessible-headings/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228267806,"owners_count":17893841,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-05T09:16:48.340Z","updated_at":"2024-12-05T09:16:49.019Z","avatar_url":"https://github.com/springload.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-accessible-headings\n\n## Why?\n\nIn order to make accessible web pages the [W3C: WCAG, WAI say](https://www.w3.org/WAI/tutorials/page-structure/headings/),\n\n\u003e Skipping heading ranks can be confusing and should be avoided where possible: Make sure that a `\u003ch2\u003e` is not followed directly by an `\u003ch4\u003e`, for example.\n\nSo an accessible app **must** not have heading levels like this...\n\n- H1\n  - H6\n- H3\n  - H1 (there should only be a single H1!)\n  - H5\n    - H4\n    - H4\n- H1\n\nInstead they should look like,\n\n- H1\n  - H2\n    - H3\n    - H3\n  - H2\n    - H3\n      - H4\n      - H4\n  - H2\n\n## Why a React library?\n\nHowever as developers of React components it's hard to make components match this semantic hierarchy. We typically hardcode heading levels, like an `\u003ch2\u003e`, or an `\u003ch3\u003e`, into a component. This would limit its flexibility and make it harder to adhere to W3C WCAG.\n\nBy using `react-accessible-headings` you can have components with **flexible headings that fit the appropriate heading level**, allowing you to more easily create accessible components, with headings that don't skip levels.\n\nCould you instead write components that accept `props` to set a heading level? Sure. But that requires manual maintenance of the hierarchy. Indenting becomes harder, and it's easier to make mistakes.\n\nThis library is 1 kilobyte (minified and compressed).\n\n## Usage\n\n```jsx\nimport React from 'react';\nimport { Level, H } from 'react-accessible-headings';\n\nexport default function () {\n  return (\n    \u003cdiv\u003e\n      \u003cH\u003eThis will be a heading 1\u003c/H\u003e\n      \u003cLevel\u003e\n        \u003cH\u003eand this a Heading 2\u003c/H\u003e\n      \u003c/Level\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n### Detecting skipped headings\n\n`react-accessible-headings` tries to encourage correct heading levels by polling the DOM for accessibility errors and printing errors to `console.error`. These errors are page-wide and not necessarily specific to `react-accessible-headings`.\n\nThere are two types of errors that are checked\n\n1. Whether there are skipped heading levels. Ie, `\u003ch1\u003e` followed by an `\u003ch3\u003e`;\n2. Whether there are multiple `\u003ch1\u003e`s in the page (there should only be a single `\u003ch1\u003e`).\n\nA `console.error()` will be printed if an error occurs.\n\nTesting in [Axe](https://www.deque.com/axe/) will also reveal this type of error.\n\nThe reason this was implemented by polling the DOM, rather than analysing the React VDOM (or something), is because only the real DOM knows the actual heading levels that screen readers will use for accessibility reasons. Pages could include headings outside of React apps that affect the heading level, so this library needs to poll the DOM.\n\n## API\n\nAll APIs have TypeScript types available.\n\n### `\u003cH\u003e` component\n\nThis component renders either `\u003ch1\u003e`, `\u003ch2\u003e`, `\u003ch3\u003e`, `\u003ch4\u003e`, `\u003ch5\u003e`, or `\u003ch6\u003e` based on how many `\u003cLevel\u003e`s were above it.\n\n`react-accessible-headings` tries to help you maintain valid heading hierarchies, so it considers it an application bug to render an `\u003ch7\u003e` (the HTML spec only has 6 heading levels). This might happen if you have too many `\u003cLevel\u003e`s above it.\n\nTo help debug the error a message will printed via `console.error` if attempting to render invalid levels such as `h7`. To resolve this error fix the wrongly nested `\u003cLevel\u003e` elements above it.\n\nAll valid props / attributes for an HTML heading are also accepted.\n\nProps: `offset`: _(Optional)_ this optional prop will override the default behaviour. The default behaviour is when you use `\u003cH\u003e` without this prop it will render the current heading level depth. If instead you want to render the `\u003cH\u003e` with a different `offset` (number) then provide this prop.\n\nSee \u003ca href=\"#examples-offset\"\u003e_Examples: The 'Offset' Example_\u003c/a\u003e for more.\n\n### `\u003cLevel\u003e` component\n\nSets a new heading level depth, by incrementing the current heading level for all children using the `\u003cH\u003e` component, or the `useLevel` hook.\n\nThis component doesn't render anything except `children`, so there's no wrapper element.\n\nProps: `value`: _(Optional)_ this optional prop will override the default behaviour. The default behaviour is when you use `\u003cLevel\u003e` without this prop it will increment the heading level by `1`. If you want to increment by a different `value` (number) that is not `1` then provide this `value` prop. You probably shouldn't be using this.\nProps: `hClassName`: _(Optional)_ this optional prop will set a className on all descendant `\u003cH\u003e`s.\n\nAn error will be logged via `console.error` if attempting to set an invalid value such as `7`, because HTML only has h1-h6.\n\n### `useLevel` context hook\n\nIf you'd like to inspect the current `level` context value then `useLevel()` which will return a **number** (integer) from 1-6. (see \u003ca href=\"#examples-uselevel\"\u003e_Examples: The 'useLevel query' Example_\u003c/a\u003e for more).\n\nAn error will be logged via `console.error` if `useLevel` resolves to an invalid heading level such as `7` and the value will be clamped from 1-6 (because `7` is an invalid heading level and it would be pointless to use that).\n\n### `useHClassName` context hook\n\nIf for some reason you'd like to inspect the current `hClassName` value, then `useHClassName()` which will return a **string** representing the className of the Heading elements in the current tree (see \u003ca href=\"#examples-usehclassname\"\u003e_Examples: The 'useHClassName' Example_\u003c/a\u003e for more).\n\n### `LevelContext` context\n\nProvides direct access to the React Context which is an object with type `undefined | { level: number, hClassName?: string }`. Note that the value may be `undefined` in which case you should infer a `level` of `1`. No clamping of valid ranges of values occurs through this direct accesss.\n\n## Further reading\n\n### Prior art\n\n[DocBook](https://docbook.org/), the ill-fated [XHTML 2](https://www.w3.org/TR/xhtml2/mod-structural.html#sec_8.5.), and [HTML5's abandoned 'outline'](http://blog.paciellogroup.com/2013/10/html5-document-outline/) had a very similar idea. Also check out the 2014 project [html5-h](https://github.com/ThePacielloGroup/html5-h).\n\n### References\n\n#### [WCAG 2: G141: Organizing a page using headings](https://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G141),\n\n\u003e To facilitate navigation and understanding of overall document structure, authors should use headings that are properly nested (e.g., h1 followed by h2, h2 followed by h2 or h3, h3 followed by h3 or h4, etc.).\n\n#### [Axe: Heading levels should only increase by one](https://dequeuniversity.com/rules/axe/3.4/heading-order)\n\n\u003e Ensure headings are in a logical order. For example, check that all headings are marked with `h1` through `h6` elements and that these are ordered hierarchically. For example, the heading level following an `h1` element should be an `h2` element, not an `h3` element.\n\n##### [Axe: Page must contain a level-one heading](https://dequeuniversity.com/rules/axe/3.0/page-has-heading-one)\n\n\u003e Generally, it is a best practice to ensure that the beginning of a page's main content starts with a h1 element, and also to ensure that the page contains only one h1 element.\n\n## Justifications \u003ca id=\"examples-toc\" href=\"#examples-toc\"\u003e#\u003c/a\u003e\n\nIs this library necessary? Could you avoid this library and perhaps make component `props` that set the heading level, or use `children` to set the heading? Sure, that works, but (arguably) that manual approach becomes a maintenance problem across a larger app. Across a whole app this alternative approach is easier to refactor and 'indent' heading levels arbitrarily without having to synchronise the correct heading level numbers across components.\n\n### The 'Card' Example \u003ca id=\"examples-card\" href=\"#examples-card\"\u003e#\u003c/a\u003e\n\nImagine you have a hypothetical 'Card' component that is coded as,\n\n```jsx\nexport function Card({ children, heading }) {\n  return (\n    \u003cdiv className=\"card\"\u003e\n      \u003ch3 className=\"card__heading\"\u003e{heading}\u003c/h3\u003e\n      {children}\n    \u003c/div\u003e\n  );\n}\n```\n\nBut then you want to make the `\u003ch3\u003e` configurable to make it either an `\u003ch2\u003e`, `\u003ch3\u003e`, or `\u003ch4\u003e`.\n\nYou might refactor the code to support that feature like this,\n\n```jsx\nexport function Card({ children, heading, headingLevel }) {\n  return (\n    \u003cdiv className=\"card\"\u003e\n      {headingLevel === 2 ? (\n        \u003ch2 className=\"card__heading\"\u003e{heading}\u003c/h2\u003e\n      ) : headingLevel === 3 ? (\n        \u003ch3 className=\"card__heading\"\u003e{heading}\u003c/h3\u003e\n      ) : headingLevel === 4 ? (\n        \u003ch4 className=\"card__heading\"\u003e{heading}\u003c/h4\u003e\n      ) : null}\n      {children}\n    \u003c/div\u003e\n  );\n}\n```\n\nor more concisely,\n\n```jsx\nexport function Card({ children, heading, headingLevel }) {\n  const Heading = `H${headingLevel}`;\n  return (\n    \u003cdiv className=\"card\"\u003e\n      \u003cHeading className=\"card__heading\"\u003e{heading}\u003c/Heading\u003e\n      {children}\n    \u003c/div\u003e\n  );\n}\n```\n\n...which is a confusingly indirect way of making a heading level, and it creates a maintenance burden on developers to know the correct level depth of a heading.\n\nAlternatively, with `react-accessible-headings` the implementation details of `\u003cCard\u003e` can stay encapsulated and look like,\n\n```jsx\nexport function Card({ children, heading }) {\n  return (\n    \u003cdiv className=\"card\"\u003e\n      \u003cH className=\"card__heading\"\u003e{heading}\u003c/H\u003e\n      {children}\n    \u003c/div\u003e\n  );\n}\n```\n\nAnd finally (for this example) let's consider another refactoring. If we want to add a new `h2` to the page and lower every other heading it's now easy to add another `\u003cLevel\u003e` wrapper to indent everything and you're done. Much easier than updating lots of `h*` numbers around the code to realign them all...\n\n```jsx\n\u003cH\u003eCards\u003c/H\u003e\n\u003cLevel\u003e\n  \u003cCard heading=\"my title\"\u003e\n    \u003cp\u003ebody\u003c/p\u003e\n  \u003c/Card\u003e\n  \u003cLevel\u003e\n    \u003cCard heading=\"my title2\"\u003e\n      \u003cp\u003ebody\u003c/p\u003e\n    \u003c/Card\u003e\n  \u003c/Level\u003e\n\u003c/Level\u003e\n```\n\nSo `react-accessible-headings` is an alternative composition technique for page headings that may make it easier to refactor and reuse code. The `\u003cLevel\u003e` concept means you only need to think about whether it's a deeper level, without having to know the specific heading level number.\n\nThat all said, having a flexible heading level may be more abstract and confusing to some developers. It's an extra thing to learn, even though it is a simple concept. It may not be appropriate for some codebases.\n\n### The 'useLevel query' Example \u003ca id=\"examples-uselevel\" href=\"#examples-uselevel\"\u003e#\u003c/a\u003e\n\nIf you want to programatically query the current level you can,\n\n```jsx\nimport { useLevel, H } from 'react-accessible-headings';\n\nexport default function () {\n  const level = useLevel(); // level is a number (integer) from 1-6\n  return (\n    \u003cdiv className={`heading--${level}`}\u003e\n      \u003cH\u003etext\u003c/H\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n### The 'Offset' Example \u003ca id=\"examples-offset\" href=\"#examples-offset\"\u003e#\u003c/a\u003e\n\nIf you want to have heading levels relative to the current level you can provide an `offset` prop,\n\n```jsx\n\u003cdiv className=\"card\"\u003e\n  \u003cH className=\"card__heading\"\u003eThis will be the current heading level\u003c/H\u003e\n  \u003cH offset={1} className=\"card__sub-heading\"\u003e\n    This will be one level deeper\n  \u003c/H\u003e\n  {children}\n\u003c/div\u003e\n```\n\nwhich is a more concise way of writing this,\n\n```jsx\n\u003cdiv className=\"card\"\u003e\n  \u003cH className=\"card__heading\"\u003eThis will be the current heading level\u003c/H\u003e\n  \u003cLevel\u003e\n    \u003cH className=\"card__sub-heading\"\u003eThis will be one level deeper\u003c/H\u003e\n  \u003c/Level\u003e\n  {children}\n\u003c/div\u003e\n```\n\nHowever `\u003cLevel\u003e` will establish a new deeper _heading level_ context whereas `offset` will not.\n\n### The 'hClassName' Example \u003ca id=\"examples-hclassname\" href=\"#examples-hclassname\"\u003e#\u003c/a\u003e\n\nIf you ever need to style multiple headings with css, you might find that your highly composable React code (for a good reason)\nhides the heading selectors from you:\n\n```css\n.card h{???} {\n  margin-top: 2em;\n}\n```\n\nIn this case you can set `className` on every `\u003cH\u003e` element and use the class selector in CSS, or as a shorthand you can provide `hClassName` prop to a `\u003cLevel\u003e` element, which will set your className on every decendant heading element in the sub-tree:\n\n```jsx\n\u003cLevel hClassName=\"heading\"\u003e\n  \u003cH\u003eMy ClassName is `heading`\u003c/H\u003e\n  \u003cH className=\"custom\"\u003eMy ClassName is `heading custom`\u003c/H\u003e\n  \u003cLevel\u003e\n    \u003cH\u003eMy ClassName is also `heading`\u003c/H\u003e\n    \u003cLevel hClassName=\"card-heading\"\u003e\n      \u003cH\u003eMine changed to `card-heading`\u003c/H\u003e\n    \u003c/Level\u003e\n  \u003c/Level\u003e\n\u003c/Level\u003e\n```\n\n### The 'useHClassName' Example \u003ca id=\"examples-usehclassname\" href=\"#examples-usehclassname\"\u003e#\u003c/a\u003e\n\nThis example shows how you can utilize `useHClassName` to extend `hClassName` instead of overriding it.\n\n```jsx\nimport { useHClassName, Level } from 'react-accessible-headings';\n\nfunction Nested() {\n  const hClassName = useHClassName(); // className declared by parent \u003cLevel\u003e\n  return \u003cLevel hClassName={`${hClassName}__with-bem-syntax`}\u003e...\u003c/Level\u003e;\n}\n\n\u003cLevel hClassName=\"heading\"\u003e\n  \u003cNested /\u003e\n\u003c/Level\u003e;\n// hClassName changed to \"heading__with-bem-syntax\"\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspringload%2Freact-accessible-headings","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspringload%2Freact-accessible-headings","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspringload%2Freact-accessible-headings/lists"}