UI annotations in Angular.
Stet is a free, MIT-licensed UI annotation library. Add hand-sketched marks to live Angular interfaces while your existing controls keep their layout, events, and semantics.
Install and make your first mark
The package is @funsaized/stet. These ESM examples use the project’s generated framework templates. Pin your installed version and inspect its capabilities before using newer options.
Import StetCircleDirective in a standalone component and bind [stetCircle] to an options object on the original control. Put the Stet CSS import in the global stylesheet so body-level overlays receive their styles.
// Adapt these sample elements to existing source; retain their semantics and handlers.
import { Component } from "@angular/core";
import { StetCircleDirective } from "@funsaized/stet/angular";
// Add @import "@funsaized/stet/style.css"; to the application's global stylesheet.
@Component({
selector: "app-annotated-action",
standalone: true,
imports: [StetCircleDirective],
template: `
<button type="button" [stetCircle]='{"seed":42,"description":"Review this action before continuing."}'>Review action</button>
`,
})
export class AnnotatedAction {}
Browse the canonical Angular templates
Keep marks in step with the interface
These lifecycle templates follow the repository checkout. Placement nudges such as labelOffsetY are unreleased; omit them with published 0.1.0 and check your installed capabilities before adapting the example.
For multiple handles, afterRenderEffect reads signal inputs and viewChild ElementRefs after rendering. Register cleanup with onCleanup before targets change. The canonical example uses a tracked @for block to model destination replacement.
// Adapt to existing controls; annotation state never controls their presence.
import { Component, ElementRef, afterRenderEffect, input, viewChild } from "@angular/core";
import { arrow, circle, sticky, type StetHandle } from "@funsaized/stet";
function attachMarks(target: Element, destination: Element | null | undefined, enabled: boolean) {
const handles: StetHandle[] = [];
const destroy = () => { for (const handle of handles.splice(0).reverse()) handle.destroy(); };
try {
if (enabled) {
handles.push(circle(target, { seed: 42, description: "Review this action before continuing." }));
handles.push(sticky(target, { seed: 43, text: "Read the consequences before continuing." }));
if (destination) handles.push(arrow(target, destination, { seed: 44, label: "Consequences are explained here.", labelOffsetY: -12 }));
}
return destroy;
} catch (error) { destroy(); throw error; }
}
// Add @import "@funsaized/stet/style.css"; to the application global stylesheet.
@Component({
selector: "app-annotated-action",
standalone: true,
template: `
<button #target type="submit">Review action</button>
@for (key of destination() > 0 ? [destination()] : []; track key) {
<p #to>Consequences of this action</p>
}
`,
})
export class AnnotatedAction {
readonly enabled = input(true);
readonly destination = input(0);
private readonly target = viewChild<ElementRef<HTMLButtonElement>>('target');
private readonly to = viewChild<ElementRef<HTMLParagraphElement>>('to');
constructor() {
afterRenderEffect(onCleanup => {
const target = this.target()?.nativeElement;
if (target) onCleanup(attachMarks(target, this.to()?.nativeElement, this.enabled()));
});
}
}
Ink, layout, and accessible meaning
Import @funsaized/stet/style.css globally. Set --stet-stroke, --stet-fill, and --stet-paper to fit your design. SVG overlays do not occupy layout space or intercept pointer input.
Keep native labels, errors, keyboard focus, and event handlers on your controls. Give meaningful marks a description; color alone cannot explain a finding. Sticky notes and arrow labels supply readable text. Optional ink motion respects reduced-motion preferences.
Stet uses browser DOM APIs. Attach handles after mount, not during server rendering. Resize and scroll tracking cannot cover every transform or top-layer dialog; read placement limits and the full API reference.
Where this fits
Annotate a settings form inside an Angular component without replacing its validation, handlers, or native focus behavior.
Reproduce and annotate a form focus bug · Document an interactive activity inbox · Try all six primitives in the playground
Work with your coding agent
The agent inspects your installed package, validates an annotation plan, and adapts a Angular template. Plans are authoring artifacts, not a runtime selector engine. Browser verification must still check placement and behavior.
Install project Agent Skills · See the complete visual handoff workflow
Machine-readable capabilities · Annotation plan schema · Full API reference