Getting StartedComponentsDesign TokensThemingTheme SynthesizerFrameworksAccessibilityUtilitiesServer RenderingBrowser SupportContributingChangelog App ShellAspect GridAuth ShellCenterClusterContainerDashboard GridDockFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkSpeed DialStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCalloutCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSeparatorSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridData TableDescription ListDiffEvent CalendarGaugeJSON TreeKanbanKey ValueLevel MeterListMeterSparklineStatStepperTableTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputOTP InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuGuided TourHover CardInline MessageLoading OverlayModalNotification PanelPopoverProgressProgress ToastSheetSnackbarSpotlightToastTooltip
ARC UI ARC Radiant Components
v3.2 Docs Components Tokens Synthesizer
Getting StartedFrameworksServer Rendering Design TokensThemingTheme SynthesizerTypographyUtilities All ComponentsAccessibilityBrowser SupportChangelogContributingStats App ShellAspect GridAuth ShellCenterClusterContainerDashboard GridDockFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkSpeed DialStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCalloutCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSeparatorSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridData TableDescription ListDiffEvent CalendarGaugeJSON TreeKanbanKey ValueLevel MeterListMeterSparklineStatStepperTableTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputOTP InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuGuided TourHover CardInline MessageLoading OverlayModalNotification PanelPopoverProgressProgress ToastSheetSnackbarSpotlightToastTooltip

Signature Pad

Canvas signature capture that participates in forms — freehand strokes serialize to a PNG data-URL and submit as the field value.

Components Signature Pad
input interactive
<arc-signature-pad>

Overview

SignaturePad is a bordered drawing surface for capturing a handwritten signature. While blank, a muted "Sign here" baseline invites the first stroke and disappears the moment ink goes down. The pen line scales with stroke velocity — slow, deliberate movement thickens it and fast flicks thin it — so a mouse or finger produces something that reads as handwriting rather than a polyline. The pen color follows `--text-primary` by default and accepts any CSS color, including a `var()` reference, through the `pen-color` attribute. The component is a real form control. Each completed stroke serializes the whole canvas to a PNG data-URL and submits it under `name`; a blank pad submits nothing, and `required` on a blank pad reports `valueMissing` like any other input. The stroke is the edit unit: `arc-input` and `arc-change` fire together once per completed stroke, never per point, each carrying the data-URL in `event.detail.value`. Once signed, a small ghost clear button appears in the top corner; clearing empties the value, fires `arc-clear`, and brings the placeholder back. An honest note on accessibility: signing by hand is inherently a pointer gesture, and the pad offers no keyboard path to produce a signature. The canvas exposes `role="img"` with an accessible name that announces its signed or empty state, it stays focusable, and the clear button is keyboard-reachable — but if your form must be completable without a pointer, you need to offer an equivalent alongside the pad, such as a type-to-sign text field or a file upload. The component does not simulate one.

Guidelines

When to use

  • Give the pad a `label` — it doubles as the accessible name of the canvas
  • Set `required` when a signature is mandatory; the pad reports `valueMissing` while blank exactly like a native input
  • Offer a keyboard-accessible alternative (type-to-sign, upload) next to the pad when the form must be completable without a pointer — the pad itself is pointer-only by nature
  • Listen for `arc-change` to persist or preview the signature; each completed stroke delivers the full, current image
  • Use `readonly` to display a captured signature that still submits but can no longer be altered

When not to use

  • Do not reflect or store the value as an attribute — a data-URL is far too large; read the `value` property or the event detail instead
  • Do not treat a signature image as proof of identity on its own — pair it with real authentication when it matters legally
  • Do not listen per-point for drawing progress; the component deliberately stays silent until a stroke completes
  • Do not hide the clear button behind your own chrome — a signer who slips needs an obvious way to start over
  • Avoid very small pads; under about 240px of width there is not enough room for a natural signing motion

Features

  • Freehand drawing with pointer capture — mouse, touch, and stylus all work, and touch never scrolls the page mid-stroke
  • Velocity-scaled pen width (up to 40% thicker or thinner around the `pen-width` base) with midpoint-smoothed curves for a natural line
  • Serializes to a PNG data-URL after every completed stroke; `toDataURL(type)` exports other formats on demand
  • Full form participation: submits under `name`, `required` + blank reports `valueMissing`, `form.reset()` restores the initial state
  • The stroke is the edit unit — `arc-input` and `arc-change` fire once per stroke, never per point
  • Ghost clear button appears once signed; clearing fires `arc-clear` and restores the "Sign here" placeholder
  • Pen color resolves CSS custom properties at stroke time, so it follows theme changes without configuration
  • Crisp on high-DPI screens — the backing store tracks `devicePixelRatio`, and completed strokes survive a resize
  • Setting `value` from script draws the image back onto the canvas, so a saved signature can be restored for review

Preview

Usage

This component requires JavaScript. No pure HTML/CSS version is available — use the Web Component directly or a framework wrapper.

<form>
  <arc-signature-pad
    label="Signature"
    name="signature"
    required
  ></arc-signature-pad>
</form>

<script>
  const pad = document.querySelector('arc-signature-pad');
  // Fired once per completed stroke with the PNG data-URL
  pad.addEventListener('arc-change', (e) => {
    console.log('Signature updated:', e.detail.value.slice(0, 40) + '…');
  });
  pad.addEventListener('arc-clear', () => {
    console.log('Signature cleared');
  });
</script>
import { SignaturePad } from '@arclux/arc-ui-react';

export default function Example() {
  return (
    <SignaturePad
      label="Signature"
      name="signature"
      required
      onArcChange={(e) => console.log('Signature:', e.detail.value)}
      onArcClear={() => console.log('Cleared')}
    />
  );
}
<script setup>
import { SignaturePad } from '@arclux/arc-ui-vue';
</script>

<template>
  <SignaturePad
    label="Signature"
    name="signature"
    required
    @arc-change="(e) => console.log('Signature:', e.detail.value)"
    @arc-clear="() => console.log('Cleared')"
  />
</template>
<script>
  import { SignaturePad } from '@arclux/arc-ui-svelte';
</script>

<SignaturePad
  label="Signature"
  name="signature"
  required
  on:arc-change={(e) => console.log('Signature:', e.detail.value)}
  on:arc-clear={() => console.log('Cleared')}
/>
import { Component } from '@angular/core';
import { SignaturePad } from '@arclux/arc-ui-angular';

@Component({
  imports: [SignaturePad],
  template: `
    <arc-signature-pad
      label="Signature"
      name="signature"
      required
      (arc-change)="onSign($event)"
      (arc-clear)="onClear()"
    ></arc-signature-pad>
  `,
})
export class MyComponent {
  onSign(e: CustomEvent) {
    console.log('Signature:', e.detail.value);
  }

  onClear() {
    console.log('Cleared');
  }
}
import { SignaturePad } from '@arclux/arc-ui-solid';

export default function Example() {
  return (
    <SignaturePad
      label="Signature"
      name="signature"
      required
      onArcChange={(e) => console.log('Signature:', e.detail.value)}
      onArcClear={() => console.log('Cleared')}
    />
  );
}
import { SignaturePad } from '@arclux/arc-ui-preact';

export default function Example() {
  return (
    <SignaturePad
      label="Signature"
      name="signature"
      required
      onArcChange={(e) => console.log('Signature:', e.detail.value)}
      onArcClear={() => console.log('Cleared')}
    />
  );
}

API

SPEED_FULL number 1.5
Stroke speed (CSS px per ms) at which the pen reaches its thinnest.
value string ''
The signature as a PNG data-URL, empty string while the pad is blank. Updated after every completed stroke. Setting it from script draws the image onto the canvas (client-side only). Not reflected — a data-URL is far too large to live in an attribute.
name string ''
Form field name the data-URL submits under.
label string ''
Label text displayed above the pad in the label typography role. Also feeds the canvas's accessible name.
disabled boolean false
Disables interaction, reducing opacity and blocking pointer events. The pad leaves the tab order.
pen-color string ''
Pen color as any CSS color, including a `var()` expression, resolved against the canvas at stroke time. Attribute: `pen-color`. Defaults to the resolved value of `--text-primary`.
pen-width number 2
Base pen width in CSS pixels. The drawn line scales with stroke velocity — up to 40% thicker on slow, deliberate movement and 40% thinner on fast flicks. Attribute: `pen-width`. Default 2.
readonly boolean false
Prevents drawing and hides the clear button while the pad stays focusable and the value still submits.
required boolean false
When true and the pad is blank, the control is invalid with `valueMissing`.
formAssociated boolean true
properties object { required: { type: Boolean, reflect: true }, readonly: { type: Boolean, reflect: true }, }
Lit merges static properties up the prototype chain, so every consumer gets these without declaring them. `required` participates in constraint validation below; `readonly` reflects for styling and is enforced by each component's interaction handlers (the mixin can't know which gestures mutate state).
autoValidates boolean true
Components that run their own constraint-validation logic (pattern checks, range checks) opt out of the automatic required sync by overriding this to false, and own the whole validity flag set instead.
form
validity
validationMessage

Events

arc-clear
Fired when the pad is cleared, via the clear button or the clear() method.
arc-input detail: { value: string }
Fired once per completed stroke with the serialized data-URL. A stroke is the edit unit — nothing fires per point while the pen is down.
arc-change detail: { value: string }
Fired when the pointer session ends and the value serializes. A stroke is a discrete gesture, so each stroke end fires arc-input then arc-change together.

See Also