i11 UIi11 registry

Tailwind Prose

A refined typography configuration for Tailwind CSS that enhances readability and consistency.

styles/typography.css
@utility prose {
	--prose-color: var(--color-stone-700);
	--prose-heading-color: var(--color-stone-950);
	--prose-strong-color: var(--color-stone-950);
	--prose-link-color: var(--color-stone-950);
	--prose-code-color: var(--color-stone-950);
	--prose-link-underline-color: var(--color-violet-400);
	--prose-th-borders: var(--color-stone-300);
	--prose-td-borders: var(--color-stone-200);
	--prose-hr-color: color-mix(in oklab, var(--color-stone-950) 10%, transparent);
	--prose-blockquote-border-color: var(--color-stone-300);
	--prose-marker-color: color-mix(
		in oklab,
		var(--color-stone-700) 25%,
		transparent
	);
	--prose-marker-decimal-color: color-mix(
		in oklab,
		var(--color-stone-700) 80%,
		transparent
	);

	&:where(.dark, .dark *) {
		--prose-color: var(--color-stone-300);
		--prose-heading-color: var(--color-white);
		--prose-strong-color: var(--color-white);
		--prose-link-color: var(--color-white);
		--prose-code-color: var(--color-white);
		--prose-marker-color: color-mix(
			in oklab,
			var(--color-stone-300) 35%,
			transparent
		);
		--prose-link-underline-color: var(--color-sky-400);
		--prose-th-borders: var(--color-stone-600);
		--prose-td-borders: var(--color-stone-700);
		--prose-hr-color: color-mix(in oklab, var(--color-white) 10%, transparent);
		--prose-blockquote-border-color: var(--color-stone-600);
	}

	@media (prefers-color-scheme: dark) {
		&:where(.system, .system *) {
			--prose-color: var(--color-stone-300);
			--prose-heading-color: var(--color-white);
			--prose-strong-color: var(--color-white);
			--prose-link-color: var(--color-white);
			--prose-code-color: var(--color-white);
			--prose-marker-color: color-mix(
				in oklab,
				var(--color-stone-300) 35%,
				transparent
			);
			--prose-link-underline-color: var(--color-sky-400);
			--prose-th-borders: var(--color-stone-600);
			--prose-td-borders: var(--color-stone-700);
			--prose-hr-color: color-mix(in oklab, var(--color-white) 10%, transparent);
			--prose-blockquote-border-color: var(--color-stone-600);
		}
	}

	color: var(--prose-color);
	font-size: var(--text-lg);

	*:where(:not(.not-prose, .not-prose *))
		+ *:where(:not(.not-prose, .not-prose *)) {
		margin-top: calc(var(--spacing) * 6);
	}

	h1:where(:not(.not-prose, .not-prose *)) {
		font-size: var(--text-2xl);
		font-weight: var(--font-weight-semibold);
		text-wrap: balance;
	}

	h2:where(:not(.not-prose, .not-prose *)) {
		font-size: var(--text-lg);
		line-height: calc(28 / 18);
		letter-spacing: -0.025em;
		color: var(--prose-code-color);
		font-weight: var(--font-weight-semibold);
		margin-top: calc(var(--spacing) * 20);
	}

	h2:has(+ h3):where(:not(.not-prose, .not-prose *)) {
		line-height: 2;
		font-weight: var(--font-weight-medium);
		font-family: var(--font-mono);
		font-variant-ligatures: none;
		letter-spacing: 0.1em;
		color: var(--prose-color);
		text-transform: uppercase;
	}

	h3:where(:not(.not-prose, .not-prose *)) {
		color: var(--prose-heading-color);
		font-weight: var(--font-weight-semibold);
		margin-top: calc(var(--spacing) * 16);
	}

	h2 + h3:where(:not(.not-prose, .not-prose *)) {
		margin-top: calc(var(--spacing) * 6);
	}

	h4:where(:not(.not-prose, .not-prose *)) {
		color: var(--prose-heading-color);
		font-weight: var(--font-weight-medium);
		margin-top: calc(var(--spacing) * 12);
	}

	:is(h2, h3, h4):where(:not(.not-prose, .not-prose *)) {
		scroll-margin-top: calc(var(--spacing) * 32);
		@variant lg {
			/* biome-ignore lint/suspicious/noDuplicateProperties: Valid overwrite in tw */
			scroll-margin-top: calc(var(--spacing) * 18);
		}
	}

	ol:where(:not(.not-prose, .not-prose *)) {
		margin-top: calc(var(--spacing) * 4);
		padding-left: calc(var(--spacing) * 6);
		list-style-type: decimal;
	}

	ol li:where(:not(.not-prose, .not-prose *)) {
		padding-left: calc(var(--spacing) * 3);
	}

	ol li + li:where(:not(.not-prose, .not-prose *)) {
		margin-top: calc(var(--spacing) * 4);
	}

	ol li:where(:not(.not-prose, .not-prose *))::marker {
		font-weight: var(--font-weight-semibold);
		color: var(--prose-marker-decimal-color);
	}

	ul:where(:not(.not-prose, .not-prose *)) {
		margin-top: calc(var(--spacing) * 4);
		padding-left: calc(var(--spacing) * 6);
		list-style-type: disc;
	}

	ul li:where(:not(.not-prose, .not-prose *)) {
		padding-left: calc(var(--spacing) * 3);
	}

	ul li:where(:not(.not-prose, .not-prose *))::marker {
		color: var(--prose-marker-color);
		font-size: calc(var(--spacing) * 6);
	}

	ul li + li:where(:not(.not-prose, .not-prose *)) {
		margin-top: var(--spacing);
	}

	a:not(:where(:is(h2, h3, h4) *)):where(:not(.not-prose, .not-prose *)) {
		color: var(--prose-link-color);
		font-weight: var(--font-weight-semibold);
		text-decoration: underline;
		text-underline-offset: 3px;
		text-decoration-color: var(--prose-link-underline-color);
		text-decoration-thickness: 1px;
		& code {
			font-weight: var(--font-weight-semibold);
		}
	}

	a:hover:where(:not(.not-prose, .not-prose *)) {
		text-decoration-thickness: 2px;
	}

	a:where(:not(.not-prose, .not-prose *)):has(> [data-media]) {
		display: block;
	}

	strong:where(:not(.not-prose, .not-prose *)) {
		color: var(--prose-strong-color);
		font-weight: var(--font-weight-semibold);
	}

	code:where(:not(.not-prose, .not-prose *)) {
		font-variant-ligatures: none;
		font-family: var(--font-mono);
		font-weight: var(--font-weight-medium);
		color: var(--prose-code-color);
	}

	:where(h2, h3, h4) code:where(:not(.not-prose, .not-prose *)) {
		font-weight: var(--font-weight-semibold);
	}

	code:where(:not(.not-prose, .not-prose *))::before,
	code:where(:not(.not-prose, .not-prose *))::after {
		display: inline;
		content: "`";
	}

	pre:where(:not(.not-prose, .not-prose *)) {
		margin-top: calc(var(--spacing) * 4);
		margin-bottom: calc(var(--spacing) * 10);
	}

	pre code * + *:where(:not(.not-prose, .not-prose *)) {
		margin-top: 0;
	}

	pre code:where(:not(.not-prose, .not-prose *))::before,
	pre code:where(:not(.not-prose, .not-prose *))::after {
		content: none;
	}

	pre code:where(:not(.not-prose, .not-prose *)) {
		font-variant-ligatures: none;
		font-family: var(--font-mono);
		font-size: var(--text-sm);
		line-height: 2;
	}

	table:where(:not(.not-prose, .not-prose *)) {
		width: 100%;
		table-layout: auto;
		margin-top: 2em;
		margin-bottom: 2em;
		font-size: var(--text-sm);
		line-height: 1.4;
	}

	thead:where(:not(.not-prose, .not-prose *)) {
		border-bottom-width: 1px;
		border-bottom-color: var(--prose-th-borders);
	}

	thead th:where(:not(.not-prose, .not-prose *)) {
		color: var(--prose-heading-color);
		font-weight: 600;
		vertical-align: bottom;
		padding-inline-end: 0.6em;
		padding-bottom: 0.8em;
		padding-inline-start: 0.6em;
	}

	thead th:first-child:where(:not(.not-prose, .not-prose *)) {
		padding-inline-start: 0;
	}

	thead th:last-child:where(:not(.not-prose, .not-prose *)) {
		padding-inline-end: 0;
	}

	tbody tr:where(:not(.not-prose, .not-prose *)) {
		border-bottom-width: 1px;
		border-bottom-color: var(--prose-td-borders);
	}

	tbody tr:last-child:where(:not(.not-prose, .not-prose *)) {
		border-bottom-width: 0;
	}

	tbody td:where(:not(.not-prose, .not-prose *)) {
		vertical-align: baseline;
	}

	tfoot:where(:not(.not-prose, .not-prose *)) {
		border-top-width: 1px;
		border-top-color: var(--prose-th-borders);
	}

	tfoot td:where(:not(.not-prose, .not-prose *)) {
		vertical-align: top;
	}

	tbody td:where(:not(.not-prose, .not-prose *)),
	tfoot td:where(:not(.not-prose, .not-prose *)) {
		padding-top: 0.8em;
		padding-inline-end: 0.6em;
		padding-bottom: 0.8em;
		padding-inline-start: 0.6em;
	}

	tbody td:first-child:where(:not(.not-prose, .not-prose *)),
	tfoot td:first-child:where(:not(.not-prose, .not-prose *)) {
		padding-inline-start: 0;
	}

	tbody td:last-child:where(:not(.not-prose, .not-prose *)),
	tfoot td:last-child:where(:not(.not-prose, .not-prose *)) {
		padding-inline-end: 0;
	}

	th:where(:not(.not-prose, .not-prose *)),
	td:where(:not(.not-prose, .not-prose *)) {
		text-align: start;
	}

	td code:where(:not(.not-prose, .not-prose *)) {
		font-size: 0.8125rem;
	}

	hr:where(:not(.not-prose, .not-prose *)) {
		border-color: var(--prose-hr-color);
		margin-block: --spacing(14);
		& + h2 {
			margin-top: --spacing(14);
		}
	}

	blockquote {
		font-style: italic;
		border-inline-start-width: 0.25rem;
		border-inline-start-color: var(--prose-blockquote-border-color);
		padding-inline-start: calc(var(--spacing) * 4);
	}

	figure:where(:not(.not-prose, .not-prose *)) {
		figcaption:where(:not(.not-prose, .not-prose *)) {
			margin-top: calc(var(--spacing) * 3);
			text-align: center;
			font-size: var(--text-sm);
			line-height: var(--text-sm--line-height);
			font-style: italic;
			color: color-mix(in oklab, var(--prose-color) 75%, transparent);
		}
	}

	:first-child:where(:not(.not-prose, .not-prose *)) {
		margin-top: 0;
	}

	:last-child:where(:not(.not-prose, .not-prose *)) {
		margin-bottom: 0;
	}
}

@utility prose-blog {
	img:where(:not(.not-prose, .not-prose *)) {
		/* biome-ignore lint/correctness/noUnknownFunction: CSS function */
		@media (max-width: theme(--breakpoint-sm)) {
			margin-inline: calc(var(--spacing) * -4);
			max-width: calc(100% + calc(var(--spacing) * 8));
		}
	}
}