svelte-headings
v0.1.3
Published
Automatic heading level management for Svelte 5. Never worry about using the wrong heading level (h1, h2, etc.) in complex component hierarchies.
Downloads
377
Maintainers
Readme
svelte-headings
Automatic heading level management for Svelte 5. Never worry about using the wrong heading level (h1, h2, etc.) in complex component hierarchies.
Inspired by react-headings.
Features
- Automatically manages heading hierarchy based on nesting depth
- Improves accessibility and SEO
- Zero runtime dependencies
- TypeScript support
- Works with Svelte 5
Installation
npm install svelte-headingsUsage
Basic Example
<script>
import { Level, H } from 'svelte-headings';
</script>
<Level element="main">
<!-- renders <h1> -->
<H>Page Title</H>
<p>Introduction text...</p>
<Level element="section" class="container">
<!-- renders <h2> -->
<H>Section Title</H>
<p>Section content...</p>
<Level element="article">
<!-- renders <h3> -->
<H>Article Title</H>
<!-- renders <h3> -->
<p>Article content...</p>
</Level>
</Level>
</Level>Output:
<main>
<h1>Page Title</h1>
<p>Introduction text...</p>
<section class="container">
<h2>Section Title</h2>
<p>Section content...</p>
<article>
<h3>Article Title</h3>
<p>Article content...</p>
</article>
</section>
</main>Level Without Wrapper Element
When you need to increment the heading level without adding a wrapper element, omit the element prop:
<Level element="section" class="container">
<H>Main Heading</H>
<p>Content under main heading</p>
<!-- no wrapper element rendered -->
<Level>
<!-- renders <h2> -->
<H>Sub Heading</H>
<p>Content under sub heading</p>
</Level>
</Level>Output:
<section class="container">
<h1>Main Heading</h1>
<p>Content under main heading</p>
<h2>Sub Heading</h2>
<p>Content under sub heading</p>
</section>Level defaults to div when attributes are added
Adding a class or any other standard HTML attribute to the Level component will automatically render a div by default without having to specify div explicitly as the element.
<!-- Level `element` will default to "div" if any attributes are applied to it. -->
<Level class="container">
<H>Heading</H>
<p>Content under heading</p>
</Level>Output:
<div class="container">
<h1>Heading</h1>
<p>Content under heading</p>
</div>Level component With multiple child H components
Placing two <H> components inside the same <Level> component will render the <H> components at the same level as each other
<Level element="section">
<H>Main Heading</H>
<p>Content under main heading</p>
<Level>
<!-- renders <h2> -->
<H>Sub Heading A</H>
<p>Content under sub heading</p>
<!-- renders <h2> -->
<H>Sub Heading B</H>
<p>Content under sub heading</p>
<!-- Only <Level> components will increase the rendered level -->
<section>
<!-- renders <h2> -->
<H>Sub Heading C</H>
<p>Content under sub heading</p>
</section>
</Level>
</Level>Output:
<section>
<h1>Main Heading</h1>
<p>Content under main heading</p>
<h2>Sub Heading A</h2>
<p>Content under sub heading</p>
<h2>Sub Heading B</h2>
<p>Content under sub heading</p>
<section>
<h2>Sub Heading C</h2>
<p>Content under sub heading</p>
</section>
</section>Reusable Components
Create components that automatically use the correct heading level based on where they're used:
<!-- Card.svelte -->
<script>
import { Level, H } from 'svelte-headings';
let { title, children } = $props();
</script>
<Level element="article" class="card">
<H>{title}</H>
{@render children?.()}
</Level><!-- App.svelte -->
<script>
import { Level, H } from 'svelte-headings';
import Card from './Card.svelte';
</script>
<Level element="main">
<!-- h1 -->
<H>Dashboard</H>
<Level element="section">
<!-- h2 -->
<H>Recent Activity</H>
<!-- h3 -->
<Card title="Project A">
<p>Card content...</p>
</Card>
<Level element="section">
<!-- h4 -->
<Card title="Project B">
<p>Card content...</p>
</Card>
</Level>
</Level>
</Level>API
<Level>
Creates a new heading level context. Each nested Level increments the heading depth by 1.
| Prop | Type | Default | Description |
| ---------------- | --------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| element | string | undefined | HTML element to render (section, article, div, etc.). If omitted, no wrapper is rendered. |
| infiniteLevels | boolean | false | When true, enables aria-level attributes for heading levels > 6. Can be set on any Level; the setting is inherited by nested Level components. |
| ...rest | | | All other props are passed to the wrapper element |
Supported elements: div, section, article, aside, nav, header, footer, main, figure, figcaption, details, summary
<H>
Renders a heading element (h1-h6) based on the current nesting level.
| Prop | Type | Default | Description |
| --------- | ---- | ------- | ------------------------------------------- |
| ...rest | | | All props are passed to the heading element |
When the nesting level exceeds 6, the behavior depends on the infiniteLevels setting (see below).
Handling Deep Nesting (Level > 6)
By default, headings are capped at <h6> when nesting exceeds 6 levels:
<!-- Default behavior: caps at h6 -->
<Level element="main">
<!-- ... 6 levels deep ... -->
<Level>
<H>Level 7 Heading</H>
<!-- renders: <h6>Level 7 Heading</h6> -->
</Level>
</Level>Enabling aria-level for Deep Nesting
For documents that genuinely require more than 6 heading levels (e.g., legal or academic content), you can enable aria-level attributes by adding infiniteLevels={true} to any Level component:
<Level element="main" infiniteLevels={true}>
<!-- ... 6 levels deep ... -->
<Level>
<H>Level 7 Heading</H>
<!-- renders: <h6 aria-level="7">Level 7 Heading</h6> -->
</Level>
</Level>You can also enable it selectively for specific branches:
<Level element="main">
<!-- ... 6 levels deep ... -->
<Level>
<H>Level 7 (capped at h6)</H>
<!-- renders: <h6>Level 7 (capped at h6)</h6> -->
</Level>
<Level infiniteLevels={true}>
<H>Level 7 (with aria-level)</H>
<!-- renders: <h6 aria-level="7">Level 7 (with aria-level)</h6> -->
</Level>
</Level>Note:
aria-levelhas inconsistent screen reader support. Consider restructuring your content to stay within 6 heading levels when possible.
TypeScript
Type definitions are included:
import type { LevelProps, HProps, LevelElement } from 'svelte-headings';License
APACHE 2.0
