Text
Heading
A section title, h1 to h6, where the level follows the page outline and the size is chosen apart from it.
- Built on
<h1> to <h6>text-wrap: balancecalc()- Client JavaScript
- None
- Works without React
- Yes, as HTML and CSS
- Source, gzipped
- 0.9 kB
- Dependencies
- React
Billing
Payment method
Past invoices
Try its options
Loading its options.
Code
import { Heading } from "@/components/equip/heading";
export default function Demo() {
return (
<div style={{ display: "grid", gap: 8, width: 224 }}>
<Heading level={2} size="xl">Billing</Heading>
<Heading level={3} size="m">Payment method</Heading>
<Heading level={4} size="xs">Past invoices</Heading>
</div>
);
}Your versionchange its options in Look, This part
HTMLworks in any framework, with the part's CSS
<div style="display:grid;gap:8px;width:224px">
<h2 class="eq-heading" data-size="xl">Billing</h2>
<h3 class="eq-heading" data-size="m">Payment method</h3>
<h4 class="eq-heading" data-size="xs">Past invoices</h4>
</div>Examples
Sizes
Each size is a multiple of --eq-text-l or --eq-text-m, so raising the text tokens raises every heading with them.
Release notes
Release notes
Release notes
Release notes
Release notes
Code
import { Heading } from "@/components/equip/heading";
export default function Example() {
return (
<div style={{ display: "grid", gap: 12 }}>
<Heading level={2} size="xl">Release notes</Heading>
<Heading level={2} size="l">Release notes</Heading>
<Heading level={2} size="m">Release notes</Heading>
<Heading level={2} size="s">Release notes</Heading>
<Heading level={2} size="xs">Release notes</Heading>
</div>
);
}HTMLworks in any framework, with the part's CSS
<div style="display:grid;gap:12px">
<h2 class="eq-heading" data-size="xl">Release notes</h2>
<h2 class="eq-heading" data-size="l">Release notes</h2>
<h2 class="eq-heading" data-size="m">Release notes</h2>
<h2 class="eq-heading" data-size="s">Release notes</h2>
<h2 class="eq-heading" data-size="xs">Release notes</h2>
</div>Page header
Team members
Everyone here can open projects in the Larder workspace. Owners can also change billing.
Code
import { Heading } from "@/components/equip/heading";
import { Button } from "@/components/equip/button";
export default function Example() {
return (
<header style={{ display: "flex", gap: 16, alignItems: "flex-end", justifyContent: "space-between", flexWrap: "wrap" }}>
<div style={{ display: "grid", gap: 4, maxWidth: 420 }}>
<Heading level={1}>Team members</Heading>
<p style={{ margin: 0, color: "var(--eq-ink-2)", fontSize: "var(--eq-text-m)", lineHeight: 1.55 }}>
Everyone here can open projects in the Larder workspace. Owners can also change billing.
</p>
</div>
<Button variant="accent">Invite people</Button>
</header>
);
}HTMLworks in any framework, with the part's CSS
<header style="display:flex;gap:16px;align-items:flex-end;justify-content:space-between;flex-wrap:wrap">
<div style="display:grid;gap:4px;max-width:420px">
<h1 class="eq-heading" data-size="xl">Team members</h1>
<p style="margin:0;color:var(--eq-ink-2);font-size:var(--eq-text-m);line-height:1.55">Everyone here can open projects in the Larder workspace. Owners can also change billing.</p>
</div>
<button type="button" class="eq-button" data-variant="accent" data-size="m">
<span class="eq-button-label">Invite people</span>
</button>
</header>Level and size apart
These card titles are h3 elements for the outline of the page, but drawn at size xs so they sit quietly inside each card.
Usage this month
Build minutes
1,840 of 3,000 used. Resets on 1 December.
Bandwidth
212 GB served from 14 regions.
Code
import { Heading } from "@/components/equip/heading";
export default function Example() {
const card = { display: "grid", gap: 4, padding: 16, border: "1px solid var(--eq-line)", borderRadius: "var(--eq-radius-l)" };
const body = { margin: 0, color: "var(--eq-ink-2)", fontSize: "var(--eq-text-s)", lineHeight: 1.5 };
return (
<section style={{ display: "grid", gap: 12 }}>
<Heading level={2} size="m">Usage this month</Heading>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))", gap: 12 }}>
<div style={card}>
<Heading level={3} size="xs">Build minutes</Heading>
<p style={body}>1,840 of 3,000 used. Resets on 1 December.</p>
</div>
<div style={card}>
<Heading level={3} size="xs">Bandwidth</Heading>
<p style={body}>212 GB served from 14 regions.</p>
</div>
</div>
</section>
);
}HTMLworks in any framework, with the part's CSS
<section style="display:grid;gap:12px">
<h2 class="eq-heading" data-size="m">Usage this month</h2>
<div style="display:grid;grid-template-columns:repeat(auto-fit, minmax(160px, 1fr));gap:12px">
<div style="display:grid;gap:4px;padding:16px;border:1px solid var(--eq-line);border-radius:var(--eq-radius-l)">
<h3 class="eq-heading" data-size="xs">Build minutes</h3>
<p style="margin:0;color:var(--eq-ink-2);font-size:var(--eq-text-s);line-height:1.5">1,840 of 3,000 used. Resets on 1 December.</p>
</div>
<div style="display:grid;gap:4px;padding:16px;border:1px solid var(--eq-line);border-radius:var(--eq-radius-l)">
<h3 class="eq-heading" data-size="xs">Bandwidth</h3>
<p style="margin:0;color:var(--eq-ink-2);font-size:var(--eq-text-s);line-height:1.5">212 GB served from 14 regions.</p>
</div>
</div>
</section>Props
| Prop | Type | Default | What it does |
|---|---|---|---|
level | 1 | 2 | 3 | 4 | 5 | 6 | none | Which element it renders. Required, and chosen from the outline of the page, not from how big it should look. |
size | "xl" | "l" | "m" | "s" | "xs" | follows level | How big it looks: 32, 24, 20, 16 or 14px at the default tokens. Level 1 is xl, 2 is l, 3 is m, 4 is s, 5 and 6 are xs. |
...rest | ComponentProps<"h2"> | none | Every native heading attribute, including id for in-page links. |
Anatomy
| root | .eq-heading | The h1 to h6 element, with its look on data-size. |
|---|
Notes
- Screen reader users jump between headings and hear their levels, so go down one level at a time and do not skip from h2 to h4 to get a smaller look; use size for that.
- Use one h1 per page, naming what the page is.
- Sizes are multiples of --eq-text-l (xs is --eq-text-m), so a product that raises its text tokens gets larger headings with no other change.
- The heading has no margin; space it with the layout around it.
- Lines are balanced with text-wrap: balance so a two-line title does not leave one word alone; engines without it wrap normally.
Source
Two files, and they are the whole part. Open either to read it, copy it straight from its bar, or add the part to your loadout and install it with everything else you picked.
components/equip/heading.tsx23 lines
import type { ComponentProps } from "react";
import "./heading.css";
type Level = 1 | 2 | 3 | 4 | 5 | 6;
type Size = "xl" | "l" | "m" | "s" | "xs";
export type HeadingProps = ComponentProps<"h2"> & {
/** Which element it renders, h1 to h6. Pick it from the outline of the page, not from the look. */
level: Level;
/** How big it looks. Defaults to the size that goes with the level. */
size?: Size;
};
const sizeForLevel: Record<Level, Size> = { 1: "xl", 2: "l", 3: "m", 4: "s", 5: "xs", 6: "xs" };
export function Heading({ level, size, className, children, ...rest }: HeadingProps) {
const Tag = `h${level}` as const;
return (
<Tag {...rest} className={["eq-heading", className].filter(Boolean).join(" ")} data-size={size ?? sizeForLevel[level]}>
{children}
</Tag>
);
}components/equip/heading.css40 lines
@layer equip {
/* The base scale stops at 16px, so heading sizes are multiples of the text tokens:
a product that raises --eq-text-l gets larger headings with it. */
.eq-heading {
--_size: calc(var(--eq-text-l) * 1.25);
--_leading: 1.25;
--_track: -0.01em;
margin: 0;
color: var(--eq-ink);
font: var(--eq-weight-strong) var(--_size) / var(--_leading) var(--eq-font);
letter-spacing: var(--_track);
text-wrap: balance;
}
.eq-heading[data-size="xl"] {
--_size: calc(var(--eq-text-l) * 2);
--_leading: 1.15;
--_track: -0.022em;
}
.eq-heading[data-size="l"] {
--_size: calc(var(--eq-text-l) * 1.5);
--_leading: 1.2;
--_track: -0.016em;
}
.eq-heading[data-size="s"] {
--_size: var(--eq-text-l);
--_leading: 1.35;
--_track: -0.005em;
}
/* The smallest step sits at body size; the weight alone sets it apart. */
.eq-heading[data-size="xs"] {
--_size: var(--eq-text-m);
--_leading: 1.4;
--_track: 0;
}
}