Tailwind Prose
A refined typography configuration for Tailwind CSS that enhances readability and consistency.
@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));
}
}
}