npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@desolint/otp-client

v0.0.1

Published

React OTP provider, hooks and inputs for Desol Int. projects

Readme

 

@desolint/otp-client

A themeable OTP verification flow for React: OtpCard for a complete screen out of the box, or headless primitives (OtpInputGroup, useOtpFlow) when you need full control over the UI. Ships ESM and CJS.

Requirements

  • React 18 or newer and react-hook-form (both peer dependencies)
  • npm 7 or newer — npm 7+ installs peer dependencies automatically

Install

npm install @desolint/otp-client

react 19.x and react-hook-form 7.x are required peer dependencies — the package uses React 19 style hoisting to ship its default design with the components, with no separate CSS import (see Styling below), and OtpInput/OtpInputGroup/OtpCard bind to react-hook-form's Controller/useForm. @desolint/otp-shared is pulled in automatically.

Quick start

Components ship their own default styling — no CSS import needed.

Wrap your app in OtpFlowProvider. navigate is called when a flow needs to show the verify screen as a page — use renderModal instead for a modal flow:

import {OtpFlowProvider} from '@desolint/otp-client';
import {useCallback} from 'react';

async function resendOtp({
  payload,
  token,
}: {
  payload?: unknown;
  token: string | null;
}) {
  const res = await fetch('/api/otp/issue', {
    method: 'POST',
    // forward flowToken if your endpoint needs it for a resend cooldown check
    body: JSON.stringify({...(payload as object), flowToken: token}),
  });
  return res.json(); // only `token` is used — the deadlines get recomputed from it
}

const REGENERATE = {otp: resendOtp};

function Providers({children}: {children: React.ReactNode}) {
  const router = useRouter();
  const navigate = useCallback(() => router.push('/verify-otp'), [router]);

  return (
    <OtpFlowProvider navigate={navigate} regenerate={REGENERATE}>
      {children}
    </OtpFlowProvider>
  );
}

Kick off a flow from your login form. submit reports that a code is required by calling reportOtpRequired:

import {useOtpFlow} from '@desolint/otp-client';

function LoginForm() {
  const router = useRouter();
  const {startFlow, reportOtpRequired} = useOtpFlow();

  const onSubmit = (values: {userId: string}) =>
    startFlow({
      submit: async (payload) => {
        const res = await fetch('/api/otp/issue', {
          method: 'POST',
          body: JSON.stringify(payload),
        });
        const data = await res.json();
        reportOtpRequired({token: data.token, requiredChannels: data.channels});
        return data;
      },
      payload: values,
      viewType: 'page',
      regenerateType: 'otp', // must match a key in `regenerate` above
      callbacks: {onSuccess: () => router.push('/dashboard')},
    });

  // ...render your form, call onSubmit on submit
}

Render the verify screen:

import {OtpCard} from '@desolint/otp-client';

export default function VerifyOtpPage() {
  return <OtpCard heading='Verify code' />;
}

That's a complete flow — issuing, the countdown, submit, resend, and error display all come from OtpCard and the provider. OtpCard also takes an optional OTP charset and a side image.

For a code that mixes letters and digits (e.g. issued with @desolint/otp-server's generateOtpCode({charset: 'alphanumeric'})), pass the matching charset so the input accepts letters too:

<OtpCard heading='Verify code' charset='alphanumeric' />

charset is 'numeric' (default), 'alpha', or 'alphanumeric' — a convenience preset that resolves to isCharAllowed; pass isCharAllowed directly for anything more specific.

Customization

If your screen needs its own layout, copy, or components instead of OtpCard's, compose it from the same primitives OtpCard is built on:

import {OtpInputGroup, useOtpCountdown, useOtpFlow} from '@desolint/otp-client';
import {useForm} from 'react-hook-form';

function VerifyOtpPage() {
  const {session, submitCode} = useOtpFlow();
  const {control, handleSubmit} = useForm();

  return (
    <form onSubmit={handleSubmit(submitCode)}>
      <OtpInputGroup
        control={control}
        fields={session.requiredChannels.map((channel) => ({name: channel}))}
      />
      <Countdown
        expiresAt={session.expiresAt}
        regenerateAllowedAfter={session.regenerateAllowedAfter}
      />
      <button type='submit'>Submit</button>
    </form>
  );
}

// Isolated so its per-second tick re-renders only this, not the form above it.
function Countdown(props: {expiresAt: number; regenerateAllowedAfter: number}) {
  const {secondsUntilExpiry} = useOtpCountdown(props);
  return <span>Expires in {secondsUntilExpiry}s</span>;
}

This has no dependency on OtpCard's config — it's built from the same primitives OtpCard uses internally.

Styling

The default design ships with the components (React 19 style hoisting) — no import required. Every component is styled through CSS custom properties, not Tailwind utilities baked into the package — override what you need, once:

:root {
  --otp-color-primary: #6d28d9;
  --otp-card-radius: 1rem;
}

For sub-part-level control, components also accept *Classes props (groupClasses, slotClasses, headingClasses, …) merged onto their defaults via className.

@desolint/otp-client/styles.css is still published, for two cases: controlling where the @layer otp rules fall in your own cascade, or a host with a strict CSP that blocks the injected <style> tag.


Development

npm install        # install dependencies (from the repo root)
npm run build      # build all three packages
npm test           # type-check + jest
npm run lint       # eslint

This package is part of the package-otp workspaces monorepo — run the commands from the repository root, not this directory.


License

MIT © Desolint — see LICENSE.