@sneekin/ui
v1.0.0
Published
Drop-in passwordless login UI for React — themed, accessible, zero dependencies. Powered by Sneek.
Readme
@sneekin/ui
Drop-in passwordless login for React, powered by Sneek.
- No API key in the browser. The component calls handlers you supply, which talk to your own backend.
- No dependencies, no stylesheet to import. One
<style>element is injected at runtime. - Light and dark, following the visitor's system setting by default.
- Accessible: labelled inputs, a polite live region for status, and a visible focus ring on every control.
Install
npm install @sneekin/uiReact 17 or newer, as a peer dependency.
Use it
import { SneekLogin, createFetchHandlers } from '@sneekin/ui';
export function Login() {
return (
<SneekLogin
{...createFetchHandlers()}
onSuccess={(session) => router.push('/app')}
/>
);
}createFetchHandlers expects two routes on your server:
| Route | Body | Returns |
| --- | --- | --- |
| POST /api/auth/request | { identifier } | { requestId, channel?, maskedIdentifier?, expiresAt? } |
| POST /api/auth/verify | { requestId, code } | whatever your session payload is |
Point them elsewhere with createFetchHandlers({ requestUrl, verifyUrl }).
Your server holds the Sneek API key and calls Sneek. The browser never sees it.
You never pick the channel
Ask Sneek to verify a person; Sneek decides how. The channel comes from the
authChannels bitmask on your application — SMS, WhatsApp, email, and push and
QR-scan as they land — and can change without a front-end release.
channel and maskedIdentifier come back on the request response for display
only. The card uses them to say what actually happened:
Sent via WhatsApp to +91***29If your backend does not forward them, the card falls back to Sent to <what the
visitor typed> and nothing else changes. Neither field is ever an input.
Theming
It follows the system colour scheme out of the box. Pin it if you prefer:
<SneekLogin theme="dark" {...handlers} />Override the single accent colour — everything else derives from it:
<SneekLogin accentColor="#7c3aed" {...handlers} />The code entry step shows the destination with an inline way to change it, and submits itself on the sixth digit — no separate verify tap needed on mobile.
Phone entry
By default the identify step opens in phone mode with a country chip —
flag, dial code, searchable picker — and a "Use email instead" link that
swaps it for a plain email field. The chip defaults to India and updates
itself if the visitor pastes a full international number (anything
starting with +):
<SneekLogin defaultCountry="GB" {...handlers} />Lock the field to just one identifier — no chip, no toggle:
<SneekLogin identifierMode="email" {...handlers} />identifierMode is 'both' | 'phone' | 'email', default 'both'.
Swap the mark, drop the sub-heading, or remove the Sneek line:
<SneekLogin
logo={<MyLogo />}
subtitle={null}
showBranding={false}
{...handlers}
/>For anything further, className and style land on the outer card, and every
control carries a stable class (.sneek-login-input, .sneek-login-button,
.sneek-login-link) you can target.
Props
| Prop | Type | Default |
| --- | --- | --- |
| requestVerification | (identifier) => Promise<VerificationRequest> | — (required) |
| verify | ({ requestId, code }) => Promise<TResult> | — (required) |
| onSuccess | (result: TResult) => void | — |
| onError | (error: unknown) => void | — |
| title | string | 'Sign in' |
| verifyTitle | string | 'Enter the code' |
| subtitle | ReactNode \| null | 'Enter your email or phone to continue.' |
| identifierMode | 'both' \| 'phone' \| 'email' | 'both' |
| defaultCountry | ISO 3166-1 alpha-2, e.g. 'GB' | 'IN' (India) |
| identifierLabel | string | 'Mobile number' / 'Email address', per mode |
| identifierPlaceholder | string | email mode only; '[email protected]' |
| theme | 'auto' \| 'light' \| 'dark' | 'auto' |
| accentColor | string | Sneek orange, tuned per mode |
| logo | ReactNode \| null | Sneek mark |
| showBranding | boolean | true |
| className / style | — | — |
Bring your own markup
If the card does not suit you, use the hook and render whatever you like. It holds the whole state machine — step, busy flags, errors, resend:
import { useSneekLogin, createFetchHandlers } from '@sneekin/ui';
const login = useSneekLogin(createFetchHandlers());
// login.step, login.identifier, login.code, login.error,
// login.channel, login.maskedIdentifier,
// login.startVerification(), login.submitCode(), login.resend(), login.back()channelLabel(login.channel) turns the raw channel into something readable
(whatsapp → WhatsApp) if you write your own status line.
License
UNLICENSED — © Abblor Tech Pvt Ltd.
