Duck Auth Card
Sign in with a code — address, then digits — with the focus, the announcement, the autofill attributes and the resend cooldown already handled.
Preview
Sign in to duck/ui
We send a six-digit code. No password to forget.
By signing in you agree to the terms and the privacy notice.
No account? Create one
Installation
One command writes the section and every component it renders.
pnpm dlx shadcn@latest add @duck/duck-auth-cardThe block lands in components/blocks/duck-auth-card.tsx. It builds on sticker-card, sticker-otp, glow-input, quack-button and holo-separator, which the CLI installs alongside it. Already installed items are skipped.
Usage
import { DuckAuthCard } from "@/components/blocks/duck-auth-card"A block is a starting point, not a widget. The props exist so the example renders with real content — once the file is in your project, hard-code what never changes and delete the rest.
Props
Everything not listed here is forwarded to the root element.
| Prop | Type | Default | Description |
|---|---|---|---|
| brand | React.ReactNode | - | Mark or wordmark above the title. |
| title | React.ReactNode | "Sign in" | The card's heading, rendered as the h1. |
| description | React.ReactNode | - | One line under it. |
| onRequestCode | (email: string) => void | Promise<unknown> | - | Called with the address. Resolve to advance to the code step; reject to stay put with the message. |
| onVerify | (code: string, email: string) => void | Promise<unknown> | - | Called once per code, whether it arrived by typing, pasting or autofill. |
| providers | DuckAuthProvider[] | - | label, icon, onSelect — OAuth buttons above the field. |
| codeLength | number | 6 | Digits in the code. Match whatever you send. |
| resendIn | number | 30 | Seconds before the resend button re-arms. |
| submitOnComplete | boolean | true | Verify as soon as the last digit lands. The button stays, for a paste that lands early. |
| error | string | - | Server-side failure. Rendered in an alert region and kept until it changes. |
| legal | React.ReactNode | - | Small print inside the card: terms, privacy. |
| footer | React.ReactNode | - | Under the card: "No account? Sign up". |
Rules
What keeps this section from turning into noise.
- Focus follows the step. Swapping a form's contents leaves focus on a button that no longer exists, which drops a keyboard user at the top of the document and tells a screen-reader user nothing.
- The step change is announced in a polite region. Nothing else in a two-step form says that the field under the caret is now a different one.
- autoComplete="one-time-code" on a single input is what makes iOS offer the SMS code. It works because StickerOtp is one real input under six cells, not six inputs.
- One verification per code. A paste that completes the strip and a press of the button are the same intent, and firing twice is how a code gets consumed before it is checked.
- Errors are announced, not just red, and a resubmit does not clear them silently.
- The submit button is the screen's one holo element. A sign-in card has nothing else competing for it.
