svelte-headings Svelte Themes

Svelte Headings

Automatic heading level management for Svelte 5. Never worry about using the wrong heading level (h1, h2, etc.) in complex component hierarchies.

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-headings

Usage

Full Feature Example

<script>
    import { Level, H } from 'svelte-headings';
</script>

<!-- renders <main> -->
<Level element="main">
    <!-- renders <h1> -->
    <H>Page Title</H>
    <p>Introduction text...</p>

    <!-- renders <section class="container"> -->
    <Level element="section" class="container">
        <!-- renders <h2> -->
        <H>Section Title</H>
        <p>Section content...</p>

        <!-- renders no wrapper element -->
        <Level>
            <!-- renders <h3> -->
            <H>Subheading A</H>
            <p>Subheading A content...</p>

            <!-- renders <h3> -->
            <H>Subheading B</H>
            <p>Subheading B content...</p>
        </Level>

        <!-- renders <div class="wrapper"> -->
        <Level class="wrapper">
            <!-- renders <h3> -->
            <H>Div Heading</H>
            <p>Div 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>

        <h3>Subheading A</h3>
        <p>Subheading A content...</p>

        <h3>Subheading B</h3>
        <p>Subheading B content...</p>

        <div class="wrapper">
            <h3>Div Heading</h3>
            <p>Div content...</p>
        </div>
    </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">
    <!-- renders <h1> -->
    <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>
        <!-- renders: <h6>Level 7 Heading</h6> -->
        <H>Level 7 Heading</H>
    </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>
        <!-- renders: <h6 aria-level="7">Level 7 Heading</h6> -->
        <H>Level 7 Heading</H>
    </Level>
</Level>

You can also enable it selectively for specific branches:

<Level element="main">
    <!-- ... 6 levels deep ... -->
    <Level>
        <!-- renders: <h6>Level 7 (capped at h6)</h6> -->
        <H>Level 7 (capped at h6)</H>
    </Level>
    <Level infiniteLevels={true}>
        <!-- renders: <h6 aria-level="7">Level 7 (with aria-level)</h6> -->
        <H>Level 7 (with aria-level)</H>
    </Level>
</Level>

Note: aria-level has 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

Top categories

Loading Svelte Themes