Skip to content

HabSectionHeader ​

The label above a block of content.

Hosts

12

The block this label is labelling.

Notification settings

3 unsaved

The block this title is titling.

eyebrow labels a block; panel titles one — and the gap beneath is the caller's
vue
<HabSectionHeader note="12">Hosts</HabSectionHeader>
<HabSectionHeader level="panel">Notification settings</HabSectionHeader>

eyebrow is the small uppercase form used over a list or a grid. panel is the sentence-case title used inside a card header.

It owns no outer spacing ​

The gap between the header and whatever it labels is yours, at the call site:

vue
<HabSectionHeader class="mb-3">Hosts</HabSectionHeader>
<HostList />

A heading cannot know what follows it, so it cannot know whether a gap is wanted, and it certainly cannot know what spacing its parent has already applied. This is the rule the rest of the kit follows — no Habitus component spaces itself from a sibling — and HabSectionHeader was the one exception until it stopped being one.

It mattered most where it was least visible. HabPanel's header slot is documented as "usually a section heading" and its band owns px-5 py-4, so the two were designed to compose — and the header's own margin double-counted against the band's padding, putting the heading 9px off the optical centre of a band that looks symmetrical. Drop it in a panel header now and it simply centres:

vue
<HabPanel>
	<template #header>
		<HabSectionHeader level="panel">Notification settings</HabSectionHeader>
	</template>
	…
</HabPanel>

An eyebrow over a list is the case that costs you a class, since that one nearly always does want the gap. mb-3 is the value it used to apply.

Register and element are separate ​

level sets how it looks. as sets what it is:

vue
<HabSectionHeader as="h3" level="panel">
	Hosts
</HabSectionHeader>

The design contract always emits an h2. A component that pins its own heading level cannot sit correctly in someone else's outline, so the element is yours — conflating the two is how a page ends up with an h2 inside an h4.

The action slot ​

Sessions

4 active

Form fields

8 fields
The action sits at the far edge, centred on the row — whatever height it is

It sits at the far edge and is centred on the row, so a control of any height lines up with the heading rather than hanging below it.

The row has two alignment contexts, and that is the reason it can take either. The heading and the note are text at two different sizes and share a baseline — centring them against each other makes the note float. The action is usually a control, and a control is a box — putting it in that same baseline group aligns its label rather than its box, which leaves a 34px button sitting 4.5px high against the heading. An inner baseline group inside a centred row gets both.

So the slot takes a link or a button interchangeably, and you do not have to tell it which.

Accessibility ​

  • Renders a real heading, so it appears in the document outline and in a screen reader's heading list. Pick as to match where the block actually sits.
  • The note is beside the heading rather than inside it, so it is not part of the heading's accessible name.
  • Nothing is rendered for note or the action slot when they are absent — an empty span in the baseline group shifts the heading's alignment.

API ​

Props ​

PropTypeDefaultDescription
asstring"h2"The heading element. The design contract always emits h2; a component that pins its own heading level cannot sit correctly in someone else's outline, so this is yours to set.
level"eyebrow" | "panel""eyebrow"Visual register.
notestringundefinedSecondary count or summary beside the heading.

Slots ​

SlotBindingsDescription
actionanyRight-aligned slot — usually a link or a small button.
defaultanyThe heading text.

Proprietary — internal use only.