pteal79/signature-capture

A NativePHP Mobile UI component for capturing handwritten signatures as SVG

Maintainers

Package info

github.com/pteal79/signature-capture

Type:nativephp-ui-plugin

pkg:composer/pteal79/signature-capture

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-08-13 05:29 UTC

This package is auto-updated.

Last update: 2026-08-13 05:37:01 UTC


README

A signature pad for NativePHP Mobile: a bounded area the user signs into with a finger or stylus, with Clear and Confirm controls. The signature comes back to PHP as an SVG document string.

Drawing is handled by a native renderer on each platform — Jetpack Compose on Android, SwiftUI on iOS — so strokes track the finger at display frame rate. PHP is only involved when a stroke ends or a control is pressed.

Installation

composer require pteal79/signature-capture

Then rebuild the app. A UI component's native half is generated at build time, so a composer update alone gives you an element that serialises fine and renders nothing:

php artisan native:run

Installing from a local path repository

Composer symlinks path repositories by default, and the app bundler resolves symlinks (getRealPath()) before working out each file's place in the archive. A symlinked plugin collapses to a single junk entry, so on device the package simply isn't there — which surfaces as Unknown native element type: signature_capture. Ask for a copy instead:

{
    "type": "path",
    "url": "../packages/pteal79/signature-capture",
    "options": { "symlink": false }
}

Composer mirrors the directory wholesale, dev dependencies included, and ignores archive.exclude when it does. So if you run this package's test suite, delete its vendor/ afterwards or exclude it from the bundle in config/nativephp.php:

'cleanup_exclude_files' => [
    'vendor/pteal79/signature-capture/vendor',
    'vendor/pteal79/signature-capture/tests',
],

Because it's a copy, changes to the package reach the app only after composer update pteal79/signature-capture.

Usage

<native:column class="p-4 gap-4">
    <native:text class="text-lg font-semibold">Please sign below</native:text>

    <native:signature-capture
        class="w-full"
        placeholder="Sign here"
        on-confirm="storeSignature"
        on-clear="signatureCleared"
    />
</native:column>
use Native\Mobile\Edge\NativeComponent;

class SignOffScreen extends NativeComponent
{
    public function storeSignature(string $svg): void
    {
        $this->delivery->update(['signature' => $svg]);
    }

    public function signatureCleared(): void
    {
        // The user wiped the pad.
    }
}

$svg is a complete SVG document — store it, render it in a PDF, or drop it straight into a Blade view with {!! $svg !!}.

Building it programmatically

use Pteal79\SignatureCapture\Elements\SignatureCapture;

SignatureCapture::make()
    ->placeholder('Sign here')
    ->strokeColor('#0F172A', dark: '#F8FAFC')
    ->strokeWidth(3)
    ->padHeight(240)
    ->confirmLabel('Accept')
    ->onConfirm('storeSignature')
    ->class('w-full');

What comes back

A single SVG document sized in density-independent units, with a viewBox matching the pad — so it scales to any size without going fuzzy:

<svg xmlns="http://www.w3.org/2000/svg" width="343" height="200" viewBox="0 0 343 200">
  <g fill="none" stroke="#111827" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
    <path d="M42.5 128 Q48.25 120.5 55.75 116.25 …"/>
  </g>
</svg>

One <path> per stroke, smoothed with quadratic curves through the midpoints of the captured samples. The on-screen drawing is built from the same walk, so the exported SVG is exactly what the user saw.

Attributes

Attribute Type Default Description
stroke-color / dark-stroke-color hex neutral Ink colour
stroke-width float 2.5 Ink width
pad-color / dark-pad-color hex white / near-black Drawing surface fill
border-color / dark-border-color hex grey Surface border, guide line and placeholder
accent-color hex #2563EB Confirm button fill
pad-height float 200 Height of the drawing area; 0 fills the space available
corner-radius float 12 Surface corner radius
placeholder string Sign here Hint shown while empty; "" shows nothing
show-guide bool true Draw the signing line across the pad
controls bool true Show the built-in Clear / Confirm buttons
confirm-label string Confirm
clear-label string Clear
disabled bool false Read-only pad, controls off
clear-token string Change the value to clear the pad from PHP
a11y-label / a11y-hint string Signature pad Screen-reader text

Colours are hex strings (#RRGGBB or #AARRGGBB), the same spelling the core elements take. Any colour left unset picks a neutral default for the active colour scheme, so the component looks right in light and dark without configuration.

Tailwind classes work as they do on any element — class="w-full" sizes and styles the component as a whole.

Events

Attribute Fires Receives
on-confirm Confirm pressed The signature as an SVG string
on-clear Clear pressed
on-change Each stroke ends, and on clear The current SVG, or '' when empty

Confirm is disabled while the pad is empty, so on-confirm never fires with an empty signature.

@submit, @dismiss and @change are accepted as directive-style aliases for on-confirm, on-clear and on-change. Other @-prefixed spellings (@confirm, @clear) do not work: the template precompiler only rewrites its own list of directive names and strips every other @event= binding before an element sees it.

Supplying your own controls

Turn the built-in buttons off, track the signature through on-change, and clear the pad by changing clear-token:

<native:signature-capture
    class="w-full"
    controls="false"
    on-change="signatureChanged"
    :clear-token="$clearToken"
/>

<native:row class="gap-2 mt-4">
    <native:button @tap="clear" class="flex-1">Clear</native:button>
    <native:button @tap="save" class="flex-1" :disabled="$signature === ''">Save</native:button>
</native:row>
public string $signature = '';

public int $clearToken = 0;

public function signatureChanged(string $svg): void
{
    $this->signature = $svg;
}

public function clear(): void
{
    $this->clearToken++;
}

public function save(): void
{
    // $this->signature holds the SVG.
}

Any value works as a clear token as long as it differs from the last one published. A PHP-driven clear doesn't fire on-clear — you already know it happened — but it does fire on-change with an empty string.

Notes

  • Filling the available height. pad-height="0" makes the pad take whatever height is left over, which needs a bounded parent — give the element a height (class="h-full") or put it in a sized container.
  • Inside a scroll view. On Android the pad claims the touch, so a stroke won't be stolen by an enclosing scroll container. On iOS a signature pad inside a scrolling ancestor can compete with the scroll gesture; prefer a fixed, non-scrolling area for signing.

Testing

The element serialises like any other, so component tests can assert it:

use Native\Mobile\Testing\Native;

Native::test(SignOffScreen::class)
    ->assertElement('signature_capture', fn ($node) => $node['props']['placeholder'] === 'Sign here');

The package's own suite covers the element's wire output:

composer test

License

MIT