Install / Frameworks

Typeset in your framework.

Install the exact version, then pick one owner for each piece of text: the React adapters for text React renders and updates, or mount() for content the framework renders. Since 4.3, mount() keeps text correct when Svelte, Vue, Solid or Lit update it in place; the React adapters are still the better owner for text React renders. Never give one piece of text two owners.

npm i -E typeset.us@4.4.0

Typeset 4.4.0. -E saves the exact version, so a release can never change your pages until you choose to upgrade.

Next.js (App Router)

Use TypesetText and TypesetRichText wherever React renders the text. They render readable HTML on the server and compose in the browser; React 18.2 and later, and every React 19.

// app/page.tsx: a server component can use the adapters directly;
// typeset.us/react is already a client component. The server HTML paints
// with native wrapping and is composed after hydration. Inside
// TypesetRichText use <a>, not next/link's <Link>: a component child keeps
// the paragraph native (native:react-component).
import { TypesetRichText, TypesetText } from 'typeset.us/react';

export default function Page() {
  return (
    <article>
      <TypesetText as="h1" text="The End of Oak Street" />
      <TypesetRichText lang="en" smartQuotes="en">
        Read <strong>the notes</strong> at <a href="/gallery">the gallery</a>.
      </TypesetRichText>
    </article>
  );
}

Next.js: Markdown and CMS content

For article HTML that React renders once, mount one controller for the page and disconnect it on unmount.

// app/components/SetArticle.tsx: for Markdown or CMS HTML that React
// renders once and never updates in place.
'use client';
import { useEffect } from 'react';
import { mount } from 'typeset.us';

export function SetArticle() {
  useEffect(() => {
    const controller = mount(document, 'article p, article h2, article li');
    return () => controller.disconnect();
  }, []);
  return null;
}

Vite + React

The same adapters, no configuration.

// Vite + React: the same adapters.
import { TypesetRichText } from 'typeset.us/react';

export function Lede() {
  return (
    <TypesetRichText as="p" lang="en">
      Plain words, <em>emphasis</em> and <a href="/more">links</a> are all fine.
    </TypesetRichText>
  );
}

Astro

Astro bundles the script and runs it once per page load. With view transitions, run it again on astro:page-load and disconnect the previous controller.

---
// src/pages/post.astro: static HTML, set once in the browser.
---
<article>
  <h1>The End of Oak Street</h1>
  <p>Paragraphs Astro renders to static HTML.</p>
</article>

<script>
  import { mount } from 'typeset.us';
  mount(document, 'article p, article h1, article li');
</script>

SvelteKit

Mount on the element that holds the content, and disconnect when the component is destroyed.

<!-- SvelteKit (Svelte 5, runes): content Svelte renders once, such as
     {@html} Markdown. In Svelte 4 or legacy mode, write export let html; -->
<script>
  import { onMount } from 'svelte';
  import { mount } from 'typeset.us';
  /** @type {{ html: string }} */
  let { html } = $props();
  /** @type {HTMLElement} */
  let article;
  onMount(() => {
    const controller = mount(article, 'p, h2, li');
    return () => controller.disconnect();
  });
</script>

<article bind:this={article}>{@html html}</article>

Vue and Nuxt

The same pattern: mount after the content renders, disconnect before unmount.

<!-- Vue or Nuxt: content Vue renders once, such as v-html Markdown.
     Without TypeScript, drop lang="ts" and the type annotations. -->
<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref } from 'vue';
import { mount } from 'typeset.us';
defineProps<{ html: string }>();
const article = ref<HTMLElement | null>(null);
let controller: ReturnType<typeof mount> | undefined;
onMounted(() => { if (article.value) controller = mount(article.value, 'p, h2, li'); });
onBeforeUnmount(() => controller?.disconnect());
</script>

<template><article ref="article" v-html="html" /></template>

Plain HTML and no-code sites

One pinned script tag sets every paragraph, heading, list item and caption.

<script src="https://typeset.us/go@4.4.0.js" integrity="sha384-OPixflfJwk7LTcwv7u1n3T6ratxF6EeTJXn3D/TnLw9bCV2qkIVogco3QFZj6l5D" crossorigin="anonymous" defer></script>

Step-by-step guides for Ghost, WordPress, Webflow, Squarespace and others: Install.