BlazorBasics.SignaturePad 1.1.1

dotnet add package BlazorBasics.SignaturePad --version 1.1.1
                    
NuGet\Install-Package BlazorBasics.SignaturePad -Version 1.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="BlazorBasics.SignaturePad" Version="1.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BlazorBasics.SignaturePad" Version="1.1.1" />
                    
Directory.Packages.props
<PackageReference Include="BlazorBasics.SignaturePad" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add BlazorBasics.SignaturePad --version 1.1.1
                    
#r "nuget: BlazorBasics.SignaturePad, 1.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package BlazorBasics.SignaturePad@1.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=BlazorBasics.SignaturePad&version=1.1.1
                    
Install as a Cake Addin
#tool nuget:?package=BlazorBasics.SignaturePad&version=1.1.1
                    
Install as a Cake Tool

Nuget Nuget

BlazorBasics.SignaturePad

A pad the user signs on with a finger, a stylus or a mouse, and a signature that comes back as a PNG or as an SVG.

There is no JavaScript in this package. Not a module, not an interop call, not a <script> tag. Capture is pointer events, drawing is SVG, and the PNG is written in C#: a rasterizer and a PNG encoder, both in this library. So there is nothing to load, nothing to dispose, nothing that breaks under prerendering, and nothing that needs a wwwroot file to be served.

It has no opinion about what you do with the signature. Storing it, uploading it, showing it on a summary page and deciding whether it is legally binding are all yours; every application concern is a parameter or a callback.

Only dependency: Microsoft.AspNetCore.Components.Web.

Install

dotnet add package BlazorBasics.SignaturePad
// Program.cs — optional. A pad works with nothing registered.
builder.Services.AddBlazorSignaturePad();

The bare minimum

@page "/sign"

<SignaturePad @bind-Value="Signature" OnSigned="Store" />

@if (Png.Length > 0)
{
    <img src="@Preview" alt="Signature" />
}

@code {
    SignatureData Signature = new();
    byte[] Png = [];
    string Preview = string.Empty;

    void Store(SignatureResult result)
    {
        Png = result.Png;
        Preview = result.PngDataUrl;
    }
}

You get a bordered box with a dashed baseline, an invitation to sign, an undo button and a clear button, and a PNG cropped to the ink after every stroke. You do not get a modal, a form field, a validation message or a place to put the file: those are the application's.

What comes out

OnSigned hands over a SignatureResult after every stroke, and only when something is listening — producing an image is real work and nobody should pay for it unasked.

result.Png          // byte[], a real PNG file
result.Svg          // string, a standalone SVG document
result.PngDataUrl   // ready for the src of an img
result.Width        // pixels
result.SignedAt

The same is available on demand through a @ref (ToPng(), ToSvg(), ToPngDataUrl(), GetResult()) and straight off the data (Signature.ToPng(), Signature.Render()), for the code that has the strokes but not the component.

The size is never asked for. It comes from the ink: the bounding box, plus a margin, times a scale. That is the only measurement available without JavaScript, and it also gives an image that is reusable whatever the pad happened to be rendered at.

<SignaturePad ExportOptions="@(new SignatureExportOptions {
                  Scale = 3,              // pixels per pad unit
                  Padding = 12,           // blank margin around the ink
                  Background = "#ffffff", // empty means transparent
                  MaximumSize = 2000 })" />

Ink that breathes

By default every point is drawn at the same thickness, which looks the same on every device. Set WidthMode and the line thins as the hand moves faster, the way a pen behaves:

<SignaturePad WidthMode="SignatureWidthMode.Velocity" MinimumWidth="1" MaximumWidth="5" />

SignatureWidthMode.Pressure follows the pointer instead, which is only meaningful with a stylus: everything else reports a flat 0.5 and the result is the constant one.

Binding, undo and reloading

Value is a SignatureData: a list of strokes, a list of points, plain and serializable. Store it and hand it back later and the pad carries on where it was left, undo stack and all.

<SignaturePad @ref="Pad" @bind-Value="Signature" ShowRedo="true" />

<button @onclick="Pad.Clear">Start over</button>
<button @onclick="Pad.Undo" disabled="@(!Pad.CanUndo)">Back one stroke</button>

To show a signature without letting anybody draw on it, there is a second component that costs no rasterizing at all — it draws the same strokes as SVG:

<SignatureView Value="Signature" Height="140">
    <EmptyContent><em>Not signed yet.</em></EmptyContent>
</SignatureView>

Customization

Slot Replaces
ToolbarContent the whole undo/redo/clear row
PromptContent the invitation shown over the empty pad
ChildContent free content laid over the drawing area, such as a printed name
EmptyContent what SignatureView shows with no ink (that component only)

Texts are a record, so a localized application builds one from its own resources. The library depends on no localization stack and looks nothing up:

new SignatureTexts { Prompt = "Firme aquí", Clear = "Borrar", Undo = "Deshacer" }

Theming is CSS custom properties on the root element, so the host never needs ::deep:

.my-pad {
    --bbsig-height: 220px;
    --bbsig-bg: #ffffff;
    --bbsig-ink: #1d4ed8;
    --bbsig-border: #d1d5db;
    --bbsig-border-active: #6b7280;
    --bbsig-radius: 10px;
    --bbsig-baseline: #d1d5db;
    --bbsig-prompt: #9ca3af;
    --bbsig-action: #374151;
    --bbsig-action-danger: #b91c1c;
}
<SignaturePad Class="my-pad" />

--bbsig-height is only written inline when you pass Height. Left alone it stays a custom property, which is what lets you make the pad shorter on a phone from a media query.

The line to sign on

Where the baseline sits is four more custom properties, and they behave the same way — set them in your own stylesheet, from a media query, or wherever else CSS reaches:

.my-pad {
    --bbsig-baseline-x: 6%;         /* where it starts, from the left edge */
    --bbsig-baseline-y: 76%;        /* how far down it sits */
    --bbsig-baseline-width: 88%;    /* how long it is */
    --bbsig-baseline-thickness: 1px;
    --bbsig-baseline-style: dashed; /* any border style: solid, dotted... */
    --bbsig-baseline: #d1d5db;      /* the color */
}

The same six are parameters, taking any CSS length, for when a single pad differs and a class is more than it is worth. Only what you pass is written inline, so the rest stays overridable:

<SignaturePad BaselineY="60%" BaselineX="0" BaselineWidth="100%" BaselineStyle="solid" />

ShowBaseline="false" leaves it out altogether.

The baseline is a positioned box and not an SVG <line> for one reason: a line is placed by its x1, y1, x2 and y2 attributes, those are not CSS properties in any browser, and the component's own stylesheet is scoped — so a rule of yours with the same selector loses on specificity anyway. Custom properties are what cascades into a scoped stylesheet, so everything worth moving is one.

Notes from the field

  • Running off the edge does not chop the stroke in two. A signature is usually bigger than the box it is asked for. Leaving with the button still held keeps the stroke open, and coming back carries on writing the same line; the part drawn outside was never seen by anybody, so the return is joined to the exit with a straight line. Releasing the button out there ends it, which is noticed on the first move that comes back with no button held — there is no pointerup to hear out of bounds without setPointerCapture, and that is JavaScript. Set ContinueOnReentry="false" for the older behaviour, where the edge ends the stroke.
  • What is drawn outside is exported even though it was never visible. With a touch pointer the browser keeps delivering moves off the element, the pad clips them, and the export crops to the ink — including the part beyond the border. Nothing here can measure the element without JavaScript, so nothing here can clamp it.
  • Everything inside the pad is transparent to the pointer. offsetX is measured from whatever the event landed on, so if the paths could receive events the coordinates would jump the moment you drew over an earlier stroke. This is why the SVG has pointer-events: none.
  • touch-action: none is not optional. Without it a finger scrolls the page instead of signing. It is in the component's own stylesheet; do not override it.
  • Pass InkColor as an expression, not as a bare identifier. InkColor=MY_COLOR in Razor hands over the literal text "MY_COLOR", because unquoted values on a string parameter are taken as literals — only non-string parameters are parsed as C#. An unreadable color makes the browser fall back to stroke: none, so the paths are in the DOM, perfectly formed, and nothing paints. Meanwhile the export still works, in black, because the rasterizer parses the color itself and falls back. Write InkColor="@MY_COLOR" or InkColor="#2e3092".
  • The strokes are not the image. Storing SignatureData lets somebody carry on drawing; storing the PNG does not. Most applications want the PNG, and then the pad is a pad again on the next visit — which is usually what you want anyway.
  • Rasterize on a pause, not on every stroke. A signature is three or four strokes in quick succession. Waiting ~400ms after the last one turns four images into one, which matters in WebAssembly.

Advanced

ISignatureRenderer is the whole export surface. Register your own and the pad uses it, which is the way in if you would rather render on the server, hand the work to a native library or watermark what comes out:

builder.Services.AddSingleton<ISignatureRenderer, MyRenderer>();
builder.Services.AddBlazorSignaturePad();

License

Free — see LICENSE.md.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.1 191 8/8/2026
1.0.0 124 8/6/2026

Version 1.1.1: The baseline is placed from CSS. It is no longer an SVG line, whose x1, y1, x2
and y2 attributes are not CSS properties and could therefore never be moved from a stylesheet,
but a box driven by the custom properties --bbsig-baseline-x, --bbsig-baseline-y,
--bbsig-baseline-width, --bbsig-baseline-thickness and --bbsig-baseline-style, with matching
BaselineX, BaselineY, BaselineWidth, BaselineThickness, BaselineStyle and BaselineColor
parameters. Defaults are unchanged; the dash pattern is now the browser's dashed border
instead of stroke-dasharray 6 6.

Version 1.0.0: First release. SignaturePad and SignatureView components, pointer capture with
no JavaScript, quadratic smoothing, constant/velocity/pressure stroke width, undo and redo,
SVG and PNG export written in C#, customizable icons/texts/slots and CSS custom properties
for theming.