I know your question is geared toward feeding a numerical prop to the component to set the heading level, but since you ended your OP with this question:
But this kind of feels like a very naive approach. Is there any better way to achieve this behaviour in svelte ?
...and to benefit future readers, here's a more robust solution to the core problem of wanting a component for generating heading tags.
You could use the Context API to generate heading tags that are completely context-aware and fully automate this, if you don't mind using a wrapper component for each section of content on the page. You would need:
- a wrapper component to set the context
- a component to generate heading tags
- the heading tag component accepts a prop so you can feed it your desired contents
Here's an example:
Section.svelte
<script>
import { setContext, getContext } from 'svelte'
let level
// if we find a context has already been set in this component tree,
// it came from a parent/ancestor instance of Section.svelte
if (getContext('headingLevel')) {
// Increment the context because this is the next nesting level
level = getContext('headingLevel') + 1
setContext('headingLevel', level)
} else {
// otherwise this instance is the first of its kind in the hierarchy
level = 2
setContext('headingLevel', level)
}
</script>
<section>
<slot />
</section>
HeadingTag.svelte
<script>
import { getContext } from 'svelte'
// prop to insert your desired contents into the heading tag
export let message
// get the context, but make sure we can't go higher than <h6>
let level = Math.min(getContext('headingLevel'), 6)
const render = () => `
<h${level}>
${message}
</h${level}>
`
</script>
{@html render()}
Then, in your other components or pages, just use it like so
MyPage.svelte
<Section>
<HeadingTag message={"hello"} />
<!-- renders <h2>hello</h2> -->
<Section>
<HeadingTag message={"hello"} />
<!-- renders <h3>hello</h3> -->
<Section>
<HeadingTag message={"hello"} />
<!-- renders <h4>hello</h4> -->
</Section>
</Section>
</Section>
<Section>
<HeadingTag message={"hello"} />
<!-- renders <h2>hello</h2> -->
</Section>
This will also work seamlessly with however your components are nested.
Note that my example has it set it up to start at <h2>
with the assumption that every page only has a single <h1>
within its <main>
, and that doesn't require this kind of automation. But you can adapt it to your use case as needed, e.g. if you want it to start off with an <h1>
at the top-level...
Section.svelte
<script>
import { setContext, getContext } from 'svelte'
let level
if (getContext('headingLevel')) {
level = getContext('headingLevel') + 1
setContext('headingLevel', level)
} else {
// this and the HTML below are the only things that changed
level = 1
setContext('headingLevel', level)
}
</script>
{#if level === 1}
<main>
<slot />
</main>
{:else}
<section>
<slot />
<section>
{/if}
Credit where it's due and for further reading for those interested: I adopted this solution in Svelte from a React-based example in this article by Heydon Pickering:
https://medium.com/@Heydon/managing-heading-levels-in-design-systems-18be9a746fa3