Prose
Wraps long-form editorial text (articles, documentation, changelogs) in a readable
Playground
Change a prop and watch it. The code below follows along.
Live
Open full cardInstall and import
Prose is in @dovetail-ds/react. Install it with React, if you haven't:
npm install @dovetail-ds/react react react-dom
import { Prose } from "@dovetail-ds/react";
Import @dovetail-ds/react/styles.css once at your app's root, then your theme after it. Setup covers the stylesheets, fonts and dark mode.
Guidelines
Wraps long-form editorial text (articles, documentation, changelogs) in a readable measure with consistent vertical rhythm.
Rules
- Cap the measure. The default 68ch is the upper bound for comfortable reading; do not raise it past 75ch to fill a wide column.
- Prose owns the gap between blocks. Do not add margins to the paragraphs inside it.
- Use
lgfor a standalone article page andmdinside product UI.smis for footnotes and sidebars only. - Content inside Prose comes from a CMS or Markdown. Do not put interactive controls in it; compose those outside.
Tradeoffs
A fixed measure leaves white space on wide screens. That space is the point: full-width text at 1400px is unreadable regardless of how empty the margins look.
Props
The typed contract, from Prose.d.ts.
import * as React from "react";
/** Readable long-form text container with a measure cap. */
export interface ProseProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
/** Body role applied to the text. @default "md" */
size?: "sm" | "md" | "lg";
/** Maximum line length. @default "68ch" */
measure?: string;
}
export declare function Prose(props: ProseProps): React.JSX.Element;
Tokens it reads
Every custom property Prose.jsx resolves, read from the source: 22 semantic. Component tier first, because that is the one to reach for.
- semantic Shared roles. Override one and everything using that role moves with it.
| Token | Tier | Declared as |
|---|---|---|
--dt-space-stack-md | semantic | var(--dt-dim-4) |
--dt-text-body-lg-family | semantic | var(--dt-font-family-sans) |
--dt-text-body-lg-line | semantic | var(--dt-line-height-lg) |
--dt-text-body-lg-size | semantic | var(--dt-font-size-lg) |
--dt-text-body-lg-tracking | semantic | var(--dt-tracking-normal) |
--dt-text-body-lg-weight | semantic | var(--dt-font-weight-regular) |
--dt-text-body-md-family | semantic | var(--dt-font-family-sans) |
--dt-text-body-md-line | semantic | var(--dt-line-height-md) |
--dt-text-body-md-size | semantic | var(--dt-font-size-md) |
--dt-text-body-md-tracking | semantic | var(--dt-tracking-normal) |
--dt-text-body-md-weight | semantic | var(--dt-font-weight-regular) |
--dt-text-body-sm-family | semantic | var(--dt-font-family-sans) |
--dt-text-body-sm-line | semantic | var(--dt-line-height-sm) |
--dt-text-body-sm-size | semantic | var(--dt-font-size-sm) |
--dt-text-body-sm-tracking | semantic | var(--dt-tracking-normal) |
--dt-text-body-sm-weight | semantic | var(--dt-font-weight-regular) |
--dt-text-body-xs-family | semantic | var(--dt-font-family-sans) |
--dt-text-body-xs-line | semantic | var(--dt-line-height-xs) |
--dt-text-body-xs-size | semantic | var(--dt-font-size-xs) |
--dt-text-body-xs-tracking | semantic | var(--dt-tracking-normal) |
--dt-text-body-xs-weight | semantic | var(--dt-font-weight-regular) |
--dt-text-primary | semantic | var(--dt-color-neutral-900) |
Source
React only, no dependencies, every value a token.
import React from "react";
export function Prose({ children, size = "md", measure = "68ch", style, ...rest }) {
const body = size === "lg" ? "lg" : size === "sm" ? "sm" : "md";
return (
<div
className="dt-prose"
style={{
maxWidth: measure,
fontFamily: `var(--dt-text-body-${body}-family)`,
fontSize: `var(--dt-text-body-${body}-size)`,
lineHeight: `var(--dt-text-body-${body}-line)`,
color: "var(--dt-text-primary)",
textWrap: "pretty",
display: "flex", flexDirection: "column", gap: "var(--dt-space-stack-md)",
...style,
}}
{...rest}
>
{children}
</div>
);
}
In context
Prose also appears on the content reference cards, alongside the rest of its family.