Skip to content
cennge-ui
GitHubv0.3.1, changelog

Prose

Rendered Markdown, MDX or CMS HTML in the theme's colours, styled by tag, with no classes in the content.

Install

Add the package, then two lines in your stylesheet. The second registers the package with Tailwind, adds the tokens and makes dark: class-based. The source is on GitHub.

pnpm add cennge-ui

/* app.css */
@import "tailwindcss";
@import "cennge-ui/styles.css";

Peer dependencies

  • react and react-dom 17, 18 or 19
  • tailwindcss 4
  • maplibre-gl 6, only for maps

To run the end-to-end tests on desktop:

pnpm exec playwright test --project=chromium --project=firefox --project=webkit
  1. Headings, links, code, lists, tables, quotes and images are styled by their tags; the HTML needs no classes.
  2. Links take the accent's text colour: switch the accent at the top of the page.
  3. A code block wider than the column scrolls sideways and becomes a tab stop, so the keyboard can scroll it too.
Live: Tab to the long command and scroll it with the arrow keys.

Field marks

Import
import { Prose } from "cennge-ui";
Size
0.7 KB gzip for this import, its dependencies aside
Pattern
Your HTML in a <div>, or in your own element through asChild
Keyboard
Arrow left Arrow right
SSR
Rendered on the server in the Next.js check; hydrates without a warning
Styles
The cennge-prose class in styles.css, in the components layer
Spacing
In em, so size scales the text and its spacing together
Tests
Unit tests; end-to-end in Chromium, Firefox and WebKit, on desktop and phone viewports: prose

When to use#

Wrap Prose around content you did not write as JSX: Markdown turned into HTML, an MDX page, a post from a CMS, a README. It styles the elements that renderers produce, headings, paragraphs, lists, task lists, code, tables, quotes, details and images, in the theme's colours and with the accent on links.

For text you lay out yourself in JSX, use Tailwind classes directly; Prose adds nothing there.

Similar#

  • Snippet is a single command with a copy button. Prose styles whole code blocks as part of the text, without a button.
  • Kbd draws a key cap with symbols. A bare <kbd> inside Prose gets a plain box.

HTML from a CMS or a renderer#

Prose is a <div>, so it takes dangerouslySetInnerHTML as well as children. A table keeps Markdown's column alignment and wraps long cells instead of pushing the page sideways. Only render HTML you trust or have sanitised.

0.3.0

Charts, in a new entry cennge-ui/charts, drawn in SVG with no chart library underneath.

Import fromWhat it adds
cennge-uiThe components
cennge-ui/chartsBarChart, LineChart, AreaChart, DonutChart, Sparkline
cennge-ui/mapMaps on MapLibre GL

Series take an eight-colour palette checked for colour blindness in both themes.

cms-html.tsx
import { Prose } from "cennge-ui";

// HTML as a CMS or a Markdown renderer returns it. Only render HTML you trust or have sanitised.
const releaseNotes = `
<h2>0.3.0</h2>
<p>Charts, in a new entry <code>cennge-ui/charts</code>, drawn in SVG with no chart library underneath.</p>
<table>
  <thead>
    <tr><th>Import from</th><th>What it adds</th></tr>
  </thead>
  <tbody>
    <tr><td><code>cennge-ui</code></td><td>The components</td></tr>
    <tr><td><code>cennge-ui/charts</code></td><td>BarChart, LineChart, AreaChart, DonutChart, Sparkline</td></tr>
    <tr><td><code>cennge-ui/map</code></td><td>Maps on MapLibre GL</td></tr>
  </tbody>
</table>
<blockquote><p>Series take an eight-colour palette checked for colour blindness in both themes.</p></blockquote>
`;

export default function ReleaseNotes() {
  return <Prose className="w-full" dangerouslySetInnerHTML={{ __html: releaseNotes }} />;
}

Sizes and your own element#

size is sm, md or lg: 14, 16 or 18 pixels. Margins and paddings are in em, so the whole block scales with it. With asChild, Prose styles your element, here an <article> and an <aside>, instead of adding a <div>.

Recolour without a rebuild

Every colour, radius and surface is a --cennge-* CSS variable. Switch the accent with one attribute: data-accent="blue", on the page or on any part of it.

sizes.tsx
import { Prose } from "cennge-ui";

export default function ThemingArticle() {
  return (
    <div className="grid w-full gap-8 sm:grid-cols-[minmax(0,1fr)_12rem]">
      <Prose size="md" asChild>
        <article>
          <h3>Recolour without a rebuild</h3>
          <p>
            Every colour, radius and surface is a <code>--cennge-*</code> CSS variable. Switch the accent with one attribute:{" "}
            <code>data-accent="blue"</code>, on the page or on any part of it.
          </p>
        </article>
      </Prose>
      <Prose size="sm" asChild>
        <aside>
          <h4>Presets</h4>
          <ul>
            <li>orange, the default</li>
            <li>blue</li>
            <li>green</li>
            <li>violet</li>
            <li>red</li>
          </ul>
        </aside>
      </Prose>
    </div>
  );
}

Components and classes inside#

The styles sit in Tailwind's components layer, below the utilities, so a utility class on an element inside wins over them, as text-sm text-muted does on the last line here. not-prose switches them off for a whole subtree, so a component keeps its own look.

Install

One package, with React and Tailwind CSS as its peer dependencies.

pnpm add cennge-ui

React 17, 18 and 19; Tailwind CSS 4.

not-prose.tsx
import { Prose, Snippet } from "cennge-ui";

export default function InstallSection() {
  return (
    <Prose className="w-full max-w-prose">
      <h3>Install</h3>
      <p>One package, with React and Tailwind CSS as its peer dependencies.</p>
      {/* not-prose: the Snippet keeps its own look instead of the prose code block's. */}
      <div className="not-prose">
        <Snippet>pnpm add cennge-ui</Snippet>
      </div>
      {/* A utility class on an element inside wins over the prose styles. */}
      <p className="text-sm text-muted">React 17, 18 and 19; Tailwind CSS 4.</p>
    </Prose>
  );
}

Without React#

The styles are a class in cennge-ui/styles.css, so server-rendered HTML can use them without the component. Add cennge-prose-sm or cennge-prose-lg for the other sizes.

<article class="cennge-prose">
  <h1>Release notes</h1>
  <p>…</p>
</article>

The component adds one thing the class cannot: a code block that scrolls sideways becomes keyboard-focusable, and stops being a tab stop when it fits again.

Props#

PropTypeDefault
size
"sm""md""lg"Text size; spacing follows it.
"md"
asChild
booleanStyle your own element instead of a div, e.g. <article>.
–

Also takes every attribute of <div>.