The Future of Web Dev
The Future of Web Dev
Hand-Drawn Text Annotations for Svelte – svelte-annotate
Draw sketchy text marks in Svelte with seven annotation types, timed sequences, multiline handling, RTL drawing, and scoped clocks.

svelte-annotate is a Svelte annotation that draws hand-drawn underlines, highlights, circles, boxes, strike-throughs, crossed-off marks, and brackets around text.
Each mark follows a numeric clock. You can control when the drawing begins and how long it takes.
Features
- 7 annotation types: underline, box, circle, highlight, strike-through, crossed-off, and bracket.
<Mark>component, Svelte attach syntax, and prop spread syntax.- Clock-based drawing sequences with configurable start and duration values.
- Shared clocks for related marks and scoped clocks for independent animations.
- Configurable color, padding, line thickness, roughness, seed, and stroke passes.
- Per-line annotations for wrapped text through
multiline. - Right-to-left drawing through the
rtloption. - Custom wrapper elements plus standard element attributes and event handlers.
Use Cases
- Documentation pages that underline terminology or circle important values as an explanation progresses.
- Product walkthroughs where several callouts draw in a controlled sequence.
- Educational interfaces that reveal emphasis alongside lesson progress.
- Presentation-style screens with timed highlights, strike-throughs, and brackets.
- Landing-page headings that use sketch-like marks for selected words or phrases.
How To Use It
Install the package:
npm i @sveltecraft/annotateBasic Usage
Create a Tween, register it as the annotation clock, and render a <Mark>:
<script>
import { Tween } from 'svelte/motion';
import { Mark, annotate } from '@sveltecraft/annotate';
const clock = new Tween(0, { duration: 2000 });
annotate(clock);
</script>
<button onclick={() => (clock.target = 2)}>
Draw annotation
</button>
<p>
Svelte makes
<Mark type="underline" color="#f97316" at={0}>
reactive interfaces
</Mark>
concise.
</p>Sequence Multiple Annotations
The at value sets the clock position where a mark begins. span controls how many clock units the drawing consumes.
Set the clock to 2 for two one-unit annotations:
<script>
import { Tween } from 'svelte/motion';
import { Mark, annotate } from '@sveltecraft/annotate';
const clock = new Tween(0, { duration: 2400 });
annotate(clock);
</script>
<button onclick={() => (clock.target = 2)}>
Play sequence
</button>
<p>
<Mark type="underline" color="#2563eb" at={0} span={1}>
Install the package
</Mark>
and
<Mark type="circle" color="#dc2626" at={1} span={1}>
animate the key phrase
</Mark>.
</p>Use Attach and Spread Syntax
Create a scoped annotator when you want to attach marks directly to existing elements:
<script>
import { Tween } from 'svelte/motion';
import { annotate } from '@sveltecraft/annotate';
const clock = new Tween(0, { duration: 2000 });
const mark = annotate.scoped(clock);
</script>
<button onclick={() => (clock.target = 2)}>
Draw
</button>
<p>
<span
{@attach mark({
type: 'underline',
color: '#f97316',
at: 0
})}
>
Attach syntax
</span>
<span
{...mark({
type: 'circle',
color: '#8b5cf6',
at: 1
})}
>
Spread syntax
</span>
</p>Choose the Element Rendered by Mark
<Mark> renders a span by default. Set as when the annotation needs another element:
<Mark
as="div"
type="box"
color="#16a34a"
class="feature-note"
at={0}
>
This annotation renders as a div.
</Mark>Run Independent Annotation Sequences
One annotate() call establishes a shared clock for plain <Mark> components. Use annotate.scoped() when different groups need their own timing:
<script>
import { Tween } from 'svelte/motion';
import { Mark, annotate } from '@sveltecraft/annotate';
const headingClock = new Tween(0, { duration: 1200 });
const noteClock = new Tween(0, { duration: 1800 });
const headingMark = annotate.scoped(headingClock);
const noteMark = annotate.scoped(noteClock);
</script>
<button onclick={() => (headingClock.target = 1)}>
Draw heading
</button>
<button onclick={() => (noteClock.target = 1)}>
Draw note
</button>
<h2>
<Mark
mark={headingMark}
type="underline"
color="#2563eb"
>
Product update
</Mark>
</h2>
<p>
<Mark
mark={noteMark}
type="highlight"
color="#fde047"
>
Available now
</Mark>
</p>Clock Inputs
annotate() accepts four clock shapes:
| Clock Input | Example | Behavior |
|---|---|---|
| Fixed number | annotate(2) | Uses a fixed clock value. |
| Getter function | annotate(() => value) | Reads the returned value on each update. |
Svelte Tween | annotate(clock) | Reads clock.current. |
| Object with a clock | annotate(scene) | Reads scene.clock. |
Annotation Options
| Option | Default | Description |
|---|---|---|
type | Required | underline, box, circle, highlight, strike-through, crossed-off, or bracket. |
color | currentColor | Stroke color. |
padding | 5 | Gap around the content. Brackets default to 12. |
strokeWidth | 2.5 | Line thickness. Ignored by highlight. |
roughness | 1.6 | Stroke wobble. Set 0 for straight lines. |
seed | 7 | Fixed drawing seed used across repaints. |
iterations | 2 | Number of stroke passes. |
rtl | false | Draws from right to left. |
brackets | ['right'] | Bracket sides to draw. |
multiline | false | Draws a mark for each wrapped line. |
at | 0 | Clock value where drawing begins. |
span | 1 | Clock units used by the drawing. |
Mark-Specific Props
| Prop | Purpose |
|---|---|
mark | Assigns an explicit scoped annotation clock. |
as | Selects the rendered HTML element. Defaults to span. |
class | Passes a CSS class to the rendered element. |
| Standard element props | Accepts values such as id, style, data-*, and handlers. |
Annotate Multiline Text
Set multiline={true} when wrapped text needs an annotation on each rendered line:
<Mark
type="highlight"
color="#fef08a"
multiline={true}
padding={2}
>
This longer sentence can wrap across several lines while each line receives
its own hand-drawn highlight.
</Mark>Draw Right to Left
Set rtl for annotations that need the opposite drawing direction:
<Mark
type="underline"
color="#0f766e"
rtl={true}
>
Right-to-left drawing
</Mark>Customize Brackets
The bracket annotation uses the right side by default. Pass the sides you want through brackets:
<Mark
type="bracket"
color="#7c3aed"
brackets={['left', 'right']}
>
Important side note
</Mark>Alternatives and Related Resources
- Svelte Bits: Official React Bits Port for Svelte Projects
- Lightweight Progress Tracking Component – svelte-scroll-tracker
- Interactive Scratch Off Components for Svelte – Scratch to Reveal
FAQs
Q: How do I animate several annotations one after another?
A: Use one clock and assign different at values. Two marks with at={0} and at={1}, each using span={1}, draw consecutively as the clock moves from 0 to 2.
Q: Can two groups of annotations use different animation timing?
A: Yes. Create each group with annotate.scoped(clock) and pass the resulting annotator through the mark prop or spread syntax.
Q: Does svelte-annotate work with wrapped text?
A: Yes. Set multiline={true} to draw individual marks around each rendered line.
Q: Can I annotate an existing element without using the Mark component?
A: Yes. A scoped annotator works through Svelte attach syntax or as a prop spread on an existing element.
Q: What happens if Mark has no clock?
A: A plain <Mark> throws when no shared or explicit clock is available. Register one with annotate(clock) or pass a scoped annotator through mark.





