Skip to content

Astro

Tegaki has an Astro component that renders full HTML at build time and hydrates on the client. It needs no JavaScript framework, and the text is visible before hydration.

npm install tegaki

Then add the Tegaki integration to your Astro config:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import tegaki from 'tegaki/astro/integration';

export default defineConfig({
  integrations: [tegaki()],
});

The component renders on the server and hands the font bundle to the browser, so the bundle’s font URLs have to work there. Vite turns them into public asset URLs for client code but leaves server code as it stands, where they point at files on the build machine. The integration makes Vite resolve them on the server too. Without it, the build warns and the page loads the handwriting in a fallback font’s widths.

---
import TegakiRenderer from 'tegaki/astro';
import bundle from 'tegaki/fonts/caveat';
---

<TegakiRenderer
  font={bundle}
  text="Hello World"
  time={{ mode: 'uncontrolled', speed: 1, loop: true }}
  style="font-size: 48px"
/>
Hello World

When using the same font across multiple components on a page, you can register the bundle once and reference it by name. This avoids duplicating the font data in the HTML output.

Use the bundle prop on a dedicated instance. It serializes the font data into a <script type="application/json"> tag and registers it on the client. Once registered, reference the font by its family name (the fullFamily field of the bundle, e.g. "Caveat"):

---
import TegakiRenderer from 'tegaki/astro';
import bundle from 'tegaki/fonts/caveat';
---

<!-- Register the bundle once (renders nothing visible) -->
<TegakiRenderer font={bundle} bundle />

<!-- Use the font by name -->
<TegakiRenderer font="Caveat" text="First line" />
<TegakiRenderer font="Caveat" text="Second line" />

Add loadFont to inject an @font-face rule so the browser starts downloading the font file immediately:

<TegakiRenderer font={bundle} bundle loadFont />

Glows, gradients, tapered ends and the rest are plugins. The component renders on the server and sends its props to the browser as JSON, so it can’t take plugin objects (they’re functions). It takes plugins by name instead: register the factories in a client <script> on the page, and name them in plugins, as a name or [name, options]:

---
import TegakiRenderer from 'tegaki/astro';
import bundle from 'tegaki/fonts/caveat';
---

<TegakiRenderer
  font={bundle}
  text="Glowing rainbow"
  time={{ mode: 'uncontrolled', speed: 1, loop: true }}
  plugins={['strokeGradient', ['glow', { radius: 0.1, color: '#00ccff' }]]}
  style="font-size: 48px"
/>

<script>
  import { glowPlugin, strokeGradientPlugin, TegakiEngine } from 'tegaki/core';

  TegakiEngine.registerPlugin(glowPlugin, strokeGradientPlugin);
</script>
Glowing rainbow

The renderer picks a plugin up when its factory is registered, so it doesn’t matter which script runs first. A name that’s still unregistered once the page has loaded is warned about in the console, and a plugin object passed to plugins is dropped, with a warning when the page renders. Your own plugins work the same way: make them with createPlugin, register them in the script, and name them by the name you gave them. See Style plugins for the built-in ones’ options.

The pressure prop (0 to 1, default 1) sets how much the ink’s width follows the pen’s pressure. The old effects prop is gone: see Migrating from effects.

  1. Build time: the Astro component calls TegakiEngine.renderElements() to produce the full HTML (canvas fallback text, overlay, sentinel). Text is visible in the initial HTML even without JavaScript.
  2. Bundle serialization: because font glyph data lives in server memory during SSR, it must be embedded into the HTML to reach the browser. When font={bundle} is passed as an object, the component serializes the bundle into a <script type="application/json"> tag. If the same font is used across multiple renderers on the same page, use the bundle prop once to serialize it and reference it by name afterward, so the bundle data isn’t duplicated in the HTML output.
  3. Client hydration: a module script reads any serialized bundle tags and registers them, then finds all [data-tegaki-options] elements, creates a TegakiEngine with adopt: true (reusing existing DOM), and starts the animation.
  4. Dynamic elements: a MutationObserver watches for elements added after initial load (e.g., from View Transitions or client-side routing) and hydrates them.

The Astro component accepts all TegakiEngineOptions except the callbacks (onComplete, onChangeTimeline), with plugins taking names only, plus:

Prop Type Description
bundle boolean Register the font bundle for client-side lookup by name
loadFont boolean Inject @font-face CSS for early font loading
class string CSS class on the container