WET Astro headings
Producing headings in a reproducible way with Tailwind and Astro requires some hoop jumping. To save you from solving the same problem in your own codebase, I share a monstrously messy Astro component I use on this very website to produce consistent headings with little effort.
The following solution makes it possible to render headings like so:
<Heading as="h1" size="lg">This isn't as big as I was hoping</Heading>
I find Typescript enumerations exhausting to work with so instead use opt for maps keyed with strings. Life is short and all about tradeoffs. If you’d like something more robust, I exchange my time for fungible life tokens.
---
import { twMerge } from "tailwind-merge";
import type { HTMLTag, Polymorphic } from "astro/types";
type Props<Tag extends HTMLTag> = Polymorphic<{ as: Tag }> & {
// Technically, we could use `keyof typeof sizes` but I couldn't care less
// about variants and enums and all that type masterbation.
size?: string;
};
const headingClasses: { [key: string]: string } = {
h1: "text-5xl sm:text-6xl lg:text-7xl font-bold",
h2: "text-4xl sm:text-5xl lg:text-6xl font-bold",
h3: "text-3xl sm:text-4xl lg:text-5xl font-bold",
h4: "text-2xl sm:text-3xl lg:text-4xl font-bold",
h5: "text-xl sm:text-2xl lg:text-3xl font-bold",
h6: "text-lg sm:text-xl lg:text-2xl font-bold",
};
const sizes: { [key: string]: string } = {
"5xl": headingClasses["h1"],
"4xl": headingClasses["h2"],
"3xl": headingClasses["h3"],
"2xl": headingClasses["h4"],
xl: headingClasses["h5"],
lg: headingClasses["h6"],
base: "text-base sm:text-lg lg:text-xl font-bold",
sm: "text-base sm:text-lg lg:text-xl font-bold",
xs: "text-sm sm:text-base lg:text-lg font-bold",
};
const { as: Tag = "h1", class: classes, size, ...props } = Astro.props;
const sizeClasses = size ? sizes[size] : headingClasses[Tag];
---
<Tag
class:list={twMerge(
"tracking-tight",
"text-neutral-900",
"dark:text-neutral-100",
sizeClasses,
classes,
)}
{...props}
>
<slot />
</Tag>
I considered removing my comment about variants and enums, but decided a peek inside my unfettered mind might make it apparent we’re all screaming on the inside, especially me.
Or, more honestly, that even in private projects where I’m the only contributor, I feel the need to excuse pragmatic decisions that I consider to be cop-outs. You’re not the only one with unrelenting standards and over-responsibility. Don’t forget to be kind to yourself!