Forms
Signature pad
Sign with a finger, pen or mouse: the ink thins as it speeds up, and a pen's pressure is felt. Anyone who can't draw can type their name instead.
Installation
npx sinaui add signature-padCopy src/lib/ui/SignaturePad.svelte, with Button.svelte, Input.svelte, Icon.svelte, announce.ts and tokens.css. No dependencies.
Examples
Signing for a loan
Draw above the line. Undo takes back the last stroke; Clear starts again.
Loan of the Canon of Medicine Bayt al-Hikma, Baghdad · returned within 40 days
Maryam al-Asturlabi
Nothing signed yet.
Not signed yet.
<script lang="ts">
import SignaturePad from '#lib/ui/SignaturePad.svelte';
let empty = $state(true);
</script>
<div class="demo">
<div class="terms">
<strong>Loan of the Canon of Medicine</strong>
<span>Bayt al-Hikma, Baghdad · returned within 40 days</span>
</div>
<SignaturePad label="Borrower's signature" signer="Maryam al-Asturlabi" bind:empty />
<p class="status" role="status">{empty ? 'Not signed yet.' : 'Signed. Ready to borrow.'}</p>
</div>
<style>
.demo {
display: grid;
gap: 0.75rem;
inline-size: min(28rem, 100%);
}
.terms {
display: grid;
}
.terms span,
.status {
margin: 0;
color: var(--ui-muted);
font-size: 0.875rem;
}
</style>In a form
With a name, the signature posts as a PNG data URL. toSVG() gives the strokes as SVG.
<script lang="ts">
import Button from '#lib/ui/Button.svelte';
import SignaturePad from '#lib/ui/SignaturePad.svelte';
let pad = $state<SignaturePad>();
let empty = $state(true);
let result = $state('');
let tried = $state(false);
function submit(e: SubmitEvent) {
e.preventDefault();
tried = true;
if (empty) return;
const svg = pad?.toSVG() ?? '';
result = svg
? `Sent as SVG (${new Intl.NumberFormat().format(svg.length)} characters) and PNG.`
: 'Sent as PNG.';
}
</script>
<form class="demo" onsubmit={submit} novalidate>
<SignaturePad
bind:this={pad}
bind:empty
label="Witness"
signer="Thābit ibn Qurra"
name="witness"
/>
{#if tried && empty}<p class="error">Sign above, or type your name instead.</p>{/if}
<Button type="submit">Witness the copy</Button>
<p class="status" role="status">{result}</p>
</form>
<style>
.demo {
display: grid;
justify-items: start;
gap: 0.75rem;
inline-size: min(28rem, 100%);
}
.demo > :global(.signature) {
justify-self: stretch;
}
.error,
.status {
margin: 0;
font-size: 0.875rem;
}
.error {
color: var(--ui-danger);
}
.status {
color: var(--ui-muted);
}
</style>Props
| Prop | Type | Default |
|---|---|---|
label | string | 'Signature' |
signer | string: printed under the line | |
value | string: a PNG data URL (bindable) | '' |
empty | boolean (bindable) | true |
name | string: posts the PNG with a form |
Bind the component to call toSVG() or toPNG(); both return '' when it's empty.
Keyboard
| Key | Does |
|---|---|
| ⌘ Z / Ctrl Z | Undo the last stroke |
| Tab | Reach "Type instead", Undo and Clear |
Accessibility
- "Type instead" swaps drawing for a name field, so it can be signed from the keyboard.
- Whether it's signed is read out as it changes.
- The ink follows the text color, so it stays readable in dark mode.