{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "username",
  "title": "Username",
  "description": "Username plugin: username-based sign-in form and availability checking field. Registers a sign-in view that accepts both username and email, and a username field with real-time availability checking rendered on the sign-up form.",
  "dependencies": [
    "@better-auth-ui/core@latest",
    "@better-auth-ui/react@latest",
    "@tanstack/react-form",
    "@tanstack/react-pacer",
    "@tanstack/react-query",
    "cn",
    "date-fns",
    "lucide-react"
  ],
  "registryDependencies": [
    "alert",
    "badge",
    "button",
    "calendar",
    "card",
    "checkbox",
    "combobox",
    "field",
    "https://better-auth-ui.com/r/radix-nova/additional-field.json",
    "input",
    "input-group",
    "popover",
    "select",
    "slider",
    "sonner",
    "spinner",
    "switch",
    "textarea"
  ],
  "files": [
    {
      "path": "examples/start-shadcn-example/src/components/auth/additional-field.tsx",
      "content": "\"use client\"\n\nimport {\n  type AdditionalField as AdditionalFieldConfig,\n  type AdditionalFieldFormValue,\n  getFormFieldErrors,\n  resolveInputType\n} from \"@better-auth-ui/core\"\nimport { useAuth, useCopyToClipboard } from \"@better-auth-ui/react\"\nimport { format } from \"date-fns\"\nimport { CalendarIcon, Check, ChevronDownIcon, Copy } from \"lucide-react\"\nimport { type ComponentType, useRef, useState } from \"react\"\nimport { toast } from \"sonner\"\n\nimport { buttonVariants } from \"@/components/ui/button\"\nimport { Calendar } from \"@/components/ui/calendar\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport {\n  Combobox,\n  ComboboxContent,\n  ComboboxEmpty,\n  ComboboxInput,\n  ComboboxItem,\n  ComboboxList\n} from \"@/components/ui/combobox\"\nimport {\n  Field,\n  FieldContent,\n  FieldError,\n  FieldLabel\n} from \"@/components/ui/field\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupInput\n} from \"@/components/ui/input-group\"\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger\n} from \"@/components/ui/popover\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue\n} from \"@/components/ui/select\"\nimport { Slider } from \"@/components/ui/slider\"\nimport { Switch } from \"@/components/ui/switch\"\nimport { Textarea } from \"@/components/ui/textarea\"\nimport { cn } from \"cn\"\n\nexport type AdditionalFieldProps = {\n  name: string\n  field: AdditionalFieldConfig\n  value: AdditionalFieldFormValue\n  onBlur: () => void\n  onChange: (value: AdditionalFieldFormValue) => void\n  isInvalid?: boolean\n  errors?: unknown[]\n  isPending?: boolean\n  /** Complete suffix appended to labels for fields that are not required. */\n  optionalLabel?: string\n}\n\nfunction valueToString(value: AdditionalFieldFormValue) {\n  if (value == null) return \"\"\n  return value instanceof Date ? value.toISOString() : String(value)\n}\n\n/** Convert a `defaultValue` into a `Date` for the calendar. */\nfunction toDate(value: unknown): Date | undefined {\n  if (value instanceof Date) return value\n  if (typeof value === \"string\") {\n    const parsed = new Date(value)\n    return Number.isNaN(parsed.getTime()) ? undefined : parsed\n  }\n  return undefined\n}\n\n/** Format a Date as `HH:mm:ss` for an `<input type=\"time\">`. */\nfunction formatTime(date: Date) {\n  const pad = (n: number) => n.toString().padStart(2, \"0\")\n  return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`\n}\n\n/**\n * Icon-only copy button used as an `InputGroupAddon`. `getValue` is invoked\n * lazily on click so the button copies the input's *live* value rather than a\n * stale snapshot — important when paired with editable inputs.\n */\nfunction CopyButton({\n  getValue,\n  isDisabled\n}: {\n  getValue: () => string | undefined\n  isDisabled?: boolean\n}) {\n  const { localization } = useAuth()\n  const { copied, copy } = useCopyToClipboard({\n    onError: (error) => {\n      console.error(\"[Better Auth UI] Copy failed\", error)\n      toast.error(localization.errors.copyFailed)\n    }\n  })\n\n  async function handleCopy() {\n    const value = getValue()\n    if (!value) return\n\n    await copy(value)\n  }\n\n  return (\n    <InputGroupButton\n      aria-label={\n        copied\n          ? localization.settings.copiedToClipboard\n          : localization.settings.copyToClipboard\n      }\n      title={\n        copied\n          ? localization.settings.copiedToClipboard\n          : localization.settings.copyToClipboard\n      }\n      onClick={handleCopy}\n      disabled={isDisabled}\n    >\n      {copied ? <Check /> : <Copy />}\n    </InputGroupButton>\n  )\n}\n\n/** Renders a single additional user field via shadcn primitives. */\nexport function AdditionalField({\n  name,\n  field: configuredField,\n  value,\n  onBlur,\n  onChange,\n  isInvalid,\n  errors,\n  isPending,\n  optionalLabel\n}: AdditionalFieldProps) {\n  const field =\n    optionalLabel && !configuredField.required\n      ? {\n          ...configuredField,\n          label: (\n            <>\n              {configuredField.label}\n              {optionalLabel}\n            </>\n          )\n        }\n      : configuredField\n  const inputType = resolveInputType(field)\n  const fieldErrors = getFormFieldErrors(errors ?? [])\n\n  if (field.render) {\n    const FieldRenderer = field.render as ComponentType<AdditionalFieldProps>\n    return (\n      <FieldRenderer\n        name={name}\n        field={field}\n        value={value}\n        onBlur={onBlur}\n        onChange={onChange}\n        isInvalid={isInvalid}\n        errors={errors}\n        isPending={isPending}\n        optionalLabel={optionalLabel}\n      />\n    )\n  }\n\n  if (inputType === \"hidden\") {\n    return (\n      <input type=\"hidden\" name={name} value={valueToString(value)} readOnly />\n    )\n  }\n\n  if (inputType === \"textarea\") {\n    return (\n      <Field data-invalid={isInvalid}>\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n        <Textarea\n          id={name}\n          name={name}\n          value={valueToString(value)}\n          onBlur={onBlur}\n          onChange={(event) => onChange(event.target.value || null)}\n          placeholder={field.placeholder}\n          required={field.required}\n          readOnly={field.readOnly}\n          disabled={isPending}\n          aria-invalid={isInvalid}\n        />\n\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"number\") {\n    const maxFractionDigits = field.formatOptions?.maximumFractionDigits\n\n    return (\n      <Field data-invalid={isInvalid}>\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n        <Input\n          id={name}\n          name={name}\n          type=\"number\"\n          inputMode={maxFractionDigits ? \"decimal\" : \"numeric\"}\n          min={field.min}\n          max={field.max}\n          step={\n            field.step ??\n            (maxFractionDigits ? 1 / 10 ** maxFractionDigits : undefined)\n          }\n          value={typeof value === \"number\" ? value : \"\"}\n          onBlur={onBlur}\n          onChange={(event) =>\n            onChange(\n              event.target.value === \"\" ? null : event.target.valueAsNumber\n            )\n          }\n          placeholder={field.placeholder}\n          required={field.required}\n          readOnly={field.readOnly}\n          disabled={isPending}\n          aria-invalid={isInvalid}\n        />\n\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"slider\") {\n    return (\n      <SliderField\n        name={name}\n        field={field}\n        value={value}\n        onBlur={onBlur}\n        onChange={onChange}\n        isInvalid={isInvalid}\n        errors={errors}\n        isPending={isPending}\n      />\n    )\n  }\n\n  if (inputType === \"switch\") {\n    return (\n      <Field data-invalid={isInvalid} orientation=\"horizontal\">\n        <Switch\n          id={name}\n          name={name}\n          checked={value === true}\n          onBlur={onBlur}\n          onCheckedChange={onChange}\n          disabled={isPending || field.readOnly}\n          aria-invalid={isInvalid}\n        />\n\n        <FieldContent>\n          <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n        </FieldContent>\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"checkbox\") {\n    return (\n      <Field data-invalid={isInvalid} orientation=\"horizontal\">\n        <Checkbox\n          id={name}\n          name={name}\n          checked={value === true}\n          onBlur={onBlur}\n          onCheckedChange={(checked) => onChange(checked === true)}\n          required={field.required}\n          disabled={isPending || field.readOnly}\n          aria-invalid={isInvalid}\n        />\n\n        <FieldContent>\n          <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n        </FieldContent>\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"select\") {\n    return (\n      <Field data-invalid={isInvalid}>\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n        <Select\n          name={name}\n          value={valueToString(value) || undefined}\n          onValueChange={(nextValue) => onChange(nextValue)}\n          required={field.required}\n          disabled={isPending || field.readOnly}\n        >\n          <SelectTrigger\n            id={name}\n            className=\"w-full\"\n            onBlur={onBlur}\n            aria-invalid={isInvalid}\n          >\n            <SelectValue placeholder={field.placeholder} />\n          </SelectTrigger>\n\n          <SelectContent>\n            {field.options?.map((option) => (\n              <SelectItem key={option.value} value={option.value}>\n                {option.label}\n              </SelectItem>\n            ))}\n          </SelectContent>\n        </Select>\n\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"combobox\") {\n    const selectedOption = field.options?.find(\n      (option) => option.value === valueToString(value)\n    )\n\n    return (\n      <Field data-invalid={isInvalid}>\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n        <Combobox\n          items={field.options ?? []}\n          name={name}\n          value={selectedOption ?? null}\n          onValueChange={(option) => onChange(option?.value ?? null)}\n          required={field.required}\n          disabled={isPending || field.readOnly}\n        >\n          <ComboboxInput\n            placeholder={field.placeholder}\n            id={name}\n            onBlur={onBlur}\n            aria-invalid={isInvalid}\n          />\n\n          <ComboboxContent>\n            <ComboboxEmpty>No items found.</ComboboxEmpty>\n\n            <ComboboxList>\n              {(option) => (\n                <ComboboxItem key={option.value} value={option}>\n                  {option.label}\n                </ComboboxItem>\n              )}\n            </ComboboxList>\n          </ComboboxContent>\n        </Combobox>\n\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  if (inputType === \"date\" || inputType === \"datetime\") {\n    return (\n      <DateInput\n        name={name}\n        field={field}\n        value={value}\n        onBlur={onBlur}\n        onChange={onChange}\n        isInvalid={isInvalid}\n        errors={errors}\n        isPending={isPending}\n      />\n    )\n  }\n\n  return (\n    <InputField\n      name={name}\n      field={field}\n      value={value}\n      onBlur={onBlur}\n      onChange={onChange}\n      isInvalid={isInvalid}\n      errors={errors}\n      isPending={isPending}\n    />\n  )\n}\n\nfunction InputField({\n  name,\n  field,\n  value,\n  onBlur,\n  onChange,\n  isInvalid,\n  errors,\n  isPending\n}: AdditionalFieldProps) {\n  const inputRef = useRef<HTMLInputElement>(null)\n  const fieldErrors = getFormFieldErrors(errors ?? [])\n\n  const hasPrefix = field.prefix != null\n  const hasSuffix = field.suffix != null || field.copyable\n\n  const isNumeric = field.type === \"number\"\n  const maxFractionDigits = field.formatOptions?.maximumFractionDigits\n  const nativeInputType = isNumeric ? \"number\" : undefined\n  const nativeInputMode = isNumeric\n    ? maxFractionDigits\n      ? \"decimal\"\n      : \"numeric\"\n    : undefined\n  const nativeStep = maxFractionDigits ? 1 / 10 ** maxFractionDigits : undefined\n\n  if (hasPrefix || hasSuffix) {\n    return (\n      <Field data-invalid={isInvalid}>\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n        <InputGroup>\n          {hasPrefix && (\n            <InputGroupAddon align=\"inline-start\">\n              {field.prefix}\n            </InputGroupAddon>\n          )}\n\n          <InputGroupInput\n            ref={inputRef}\n            id={name}\n            name={name}\n            type={nativeInputType}\n            inputMode={nativeInputMode}\n            step={nativeStep}\n            value={valueToString(value)}\n            onBlur={onBlur}\n            onChange={(event) => onChange(event.target.value || null)}\n            placeholder={field.placeholder}\n            required={field.required}\n            readOnly={field.readOnly}\n            disabled={isPending}\n            aria-invalid={isInvalid}\n          />\n\n          {field.copyable ? (\n            <InputGroupAddon align=\"inline-end\">\n              <CopyButton\n                getValue={() => inputRef.current?.value}\n                isDisabled={isPending}\n              />\n            </InputGroupAddon>\n          ) : (\n            field.suffix != null && (\n              <InputGroupAddon align=\"inline-end\">\n                {field.suffix}\n              </InputGroupAddon>\n            )\n          )}\n        </InputGroup>\n\n        <FieldError errors={fieldErrors} />\n      </Field>\n    )\n  }\n\n  return (\n    <Field data-invalid={isInvalid}>\n      <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n      <Input\n        id={name}\n        name={name}\n        type={nativeInputType}\n        inputMode={nativeInputMode}\n        step={nativeStep}\n        value={valueToString(value)}\n        onBlur={onBlur}\n        onChange={(event) => onChange(event.target.value || null)}\n        placeholder={field.placeholder}\n        required={field.required}\n        readOnly={field.readOnly}\n        disabled={isPending}\n        aria-invalid={isInvalid}\n      />\n\n      <FieldError errors={fieldErrors} />\n    </Field>\n  )\n}\n\n/**\n * Slider field. Radix Slider doesn't render the current value, so we render\n * it next to the label and control the state to keep the displayed value in\n * sync. The selected value is submitted via the underlying Radix `name` prop.\n */\nfunction SliderField({\n  name,\n  field,\n  value,\n  onBlur,\n  onChange,\n  isInvalid,\n  errors,\n  isPending\n}: AdditionalFieldProps) {\n  const maxFractionDigits = field.formatOptions?.maximumFractionDigits\n  const min = field.min ?? 0\n  const max = field.max ?? 100\n  const step =\n    field.step ?? (maxFractionDigits ? 1 / 10 ** maxFractionDigits : 1)\n  const numericValue = typeof value === \"number\" ? value : min\n  const fieldErrors = getFormFieldErrors(errors ?? [])\n\n  const formatter = new Intl.NumberFormat(undefined, field.formatOptions)\n\n  return (\n    <Field data-invalid={isInvalid}>\n      <div className=\"flex items-center justify-between gap-2\">\n        <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n        <span className=\"text-sm text-muted-foreground tabular-nums\">\n          {formatter.format(numericValue)}\n        </span>\n      </div>\n\n      <Slider\n        id={name}\n        name={name}\n        value={[numericValue]}\n        onBlur={onBlur}\n        onValueChange={(nextValue) =>\n          onChange((Array.isArray(nextValue) ? nextValue[0] : nextValue) ?? min)\n        }\n        min={min}\n        max={max}\n        step={step}\n        disabled={isPending || field.readOnly}\n        aria-invalid={isInvalid}\n      />\n\n      <FieldError errors={fieldErrors} />\n    </Field>\n  )\n}\n\n/**\n * Date / datetime input. Composes `Popover` + `Calendar` for the date and\n * (optionally) `<input type=\"time\">` for the time. Submits the combined ISO\n * value via a hidden `<input>` so it shows up in `FormData`.\n */\nfunction DateInput({\n  name,\n  field,\n  value,\n  onBlur,\n  onChange,\n  isInvalid,\n  errors,\n  isPending\n}: AdditionalFieldProps) {\n  const { localization } = useAuth()\n  const inputType = resolveInputType(field)\n  const isDateTime = inputType === \"datetime\"\n  const fieldErrors = getFormFieldErrors(errors ?? [])\n\n  const date = toDate(value)\n  const [time, setTime] = useState<string>(\n    isDateTime && date ? formatTime(date) : \"\"\n  )\n  const [open, setOpen] = useState(false)\n\n  // Compose the hidden form value: ISO date for \"date\", ISO datetime for\n  // \"datetime\" (date + time).\n  return (\n    <Field data-invalid={isInvalid}>\n      <FieldLabel htmlFor={`${name}-date`}>{field.label}</FieldLabel>\n\n      <div className=\"relative flex gap-2\">\n        <Popover open={open} onOpenChange={setOpen}>\n          <PopoverTrigger\n            type=\"button\"\n            id={`${name}-date`}\n            data-empty={!date}\n            aria-invalid={isInvalid}\n            aria-required={field.required}\n            onBlur={onBlur}\n            disabled={isPending || field.readOnly}\n            className={cn(\n              buttonVariants({ variant: \"outline\" }),\n              \"flex-1 justify-between font-normal\",\n              \"data-[empty=true]:text-muted-foreground\"\n            )}\n          >\n            {date ? format(date, \"PPP\") : <span>{field.placeholder}</span>}\n\n            {isDateTime ? <ChevronDownIcon /> : <CalendarIcon />}\n          </PopoverTrigger>\n\n          <PopoverContent className=\"w-auto overflow-hidden p-0\" align=\"start\">\n            <Calendar\n              mode=\"single\"\n              selected={date}\n              defaultMonth={date}\n              captionLayout=\"dropdown\"\n              onSelect={(value) => {\n                if (!value) {\n                  onChange(null)\n                } else {\n                  const nextValue = new Date(value)\n                  if (isDateTime && time.trim()) {\n                    const [hours = \"0\", minutes = \"0\", seconds = \"0\"] =\n                      time.split(\":\")\n                    nextValue.setHours(\n                      Number(hours),\n                      Number(minutes),\n                      Number(seconds),\n                      0\n                    )\n                  } else {\n                    nextValue.setHours(0, 0, 0, 0)\n                  }\n                  onChange(nextValue)\n                }\n                if (!isDateTime) setOpen(false)\n              }}\n            />\n          </PopoverContent>\n        </Popover>\n\n        {isDateTime && (\n          <Field className=\"w-32\">\n            <FieldLabel htmlFor={`${name}-time`} className=\"sr-only\">\n              {localization.settings.time}\n            </FieldLabel>\n\n            <Input\n              type=\"time\"\n              id={`${name}-time`}\n              step=\"1\"\n              value={time}\n              onChange={(event) => {\n                const nextTime = event.target.value\n                setTime(nextTime)\n                if (!date) return\n                const nextValue = new Date(date)\n                const [hours = \"0\", minutes = \"0\", seconds = \"0\"] =\n                  nextTime.split(\":\")\n                nextValue.setHours(\n                  Number(hours),\n                  Number(minutes),\n                  Number(seconds),\n                  0\n                )\n                onChange(nextValue)\n              }}\n              disabled={isPending || field.readOnly}\n              className=\"appearance-none bg-background [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none\"\n            />\n          </Field>\n        )}\n      </div>\n\n      <FieldError errors={fieldErrors} />\n    </Field>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/additional-field.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/auth-form.tsx",
      "content": "\"use client\"\n\nimport {\n  type AdditionalField as AdditionalFieldConfig,\n  type AdditionalFieldFormValue,\n  DEFAULT_ADDITIONAL_FIELD_VALIDATION_DEBOUNCE_MS,\n  getFormFieldErrors,\n  normalizeAuthFormServerError,\n  validateAdditionalFieldRequired,\n  validateAdditionalFieldValue\n} from \"@better-auth-ui/core\"\nimport {\n  type AnyFormApi,\n  createFormHook,\n  createFormHookContexts\n} from \"@tanstack/react-form\"\nimport {\n  type ComponentProps,\n  type FormEvent,\n  type ReactNode,\n  useRef\n} from \"react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Field,\n  FieldDescription,\n  FieldError,\n  FieldLabel\n} from \"@/components/ui/field\"\nimport { Input } from \"@/components/ui/input\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { AdditionalField, type AdditionalFieldProps } from \"./additional-field\"\n\nconst { fieldContext, formContext, useFieldContext, useFormContext } =\n  createFormHookContexts()\n\nconst DEFAULT_AUTH_FORM_SERVER_ERROR = \"Unable to submit this form. Try again.\"\n\nexport function focusFirstInvalidAuthFormControl(form: HTMLFormElement) {\n  requestAnimationFrame(() => {\n    form\n      .querySelector<HTMLElement>(\n        '[aria-invalid=\"true\"]:not([disabled]), :invalid:not([disabled])'\n      )\n      ?.focus()\n  })\n}\n\nfunction AuthFormFieldError() {\n  const field = useFieldContext<unknown>()\n  const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid\n\n  if (!isInvalid) return null\n\n  const errors = getFormFieldErrors(field.state.meta.errors)\n\n  return errors.length > 0 ? <FieldError errors={errors} /> : null\n}\n\nfunction AuthFormServerError() {\n  const form = useFormContext()\n\n  return (\n    <form.Subscribe selector={(state) => state.errorMap.onServer}>\n      {(error) => {\n        const formError =\n          error && typeof error === \"object\" && \"form\" in error\n            ? error.form\n            : error\n        const errors = getFormFieldErrors(formError ? [formError] : [])\n        return errors.length > 0 ? <FieldError errors={errors} /> : null\n      }}\n    </form.Subscribe>\n  )\n}\n\nexport function setAuthFormServerError(\n  form: AnyFormApi,\n  error: unknown,\n  fallbackMessage: string\n) {\n  const normalized = normalizeAuthFormServerError(error, fallbackMessage)\n  form.setErrorMap({\n    onServer: {\n      fields: normalized.fields ?? {},\n      form: normalized.form\n    }\n  })\n}\n\nexport function clearAuthFormServerError(form: AnyFormApi) {\n  form.setErrorMap({ onServer: { fields: {} } })\n}\n\nexport function clearAuthFormFieldServerError(\n  form: AnyFormApi,\n  fieldName: string\n) {\n  form.setErrorMap({ onServer: undefined })\n  if (!fieldName) return\n\n  const fieldMeta = form.getFieldMeta(fieldName as never)\n  if (!fieldMeta?.errorMap.onServer) return\n\n  form.setFieldMeta(fieldName as never, (current = fieldMeta) => ({\n    ...current,\n    errorMap: { ...current.errorMap, onServer: undefined },\n    errorSourceMap: { ...current.errorSourceMap, onServer: undefined }\n  }))\n}\n\nexport async function runAuthFormAction(\n  form: AnyFormApi,\n  action: () => Promise<unknown>,\n  serverErrorMessage = DEFAULT_AUTH_FORM_SERVER_ERROR\n) {\n  clearAuthFormServerError(form)\n  try {\n    await action()\n    return true\n  } catch (error) {\n    if (!form.state.errorMap.onServer) {\n      setAuthFormServerError(form, error, serverErrorMessage)\n    }\n    return false\n  }\n}\n\nexport async function submitAuthForm(\n  form: AnyFormApi,\n  serverErrorMessage = DEFAULT_AUTH_FORM_SERVER_ERROR\n) {\n  clearAuthFormServerError(form)\n  try {\n    await form.handleSubmit()\n    return form.state.isValid\n  } catch (error) {\n    if (!form.state.errorMap.onServer) {\n      setAuthFormServerError(form, error, serverErrorMessage)\n    }\n    return false\n  }\n}\n\ntype AuthFormRootProps = Omit<ComponentProps<\"form\">, \"onSubmit\"> & {\n  onBeforeSubmit?: () => void\n  serverErrorMessage?: string\n}\n\nfunction AuthFormRoot({\n  children,\n  onBeforeSubmit,\n  onInput,\n  serverErrorMessage = DEFAULT_AUTH_FORM_SERVER_ERROR,\n  ...props\n}: AuthFormRootProps) {\n  const form = useFormContext()\n  const submittingRef = useRef(false)\n\n  async function submit(event: FormEvent<HTMLFormElement>) {\n    event.preventDefault()\n    if (submittingRef.current || form.state.isSubmitting) return\n\n    const formElement = event.currentTarget\n    onBeforeSubmit?.()\n    submittingRef.current = true\n    try {\n      const isValid = await submitAuthForm(form, serverErrorMessage)\n      if (!isValid) focusFirstInvalidAuthFormControl(formElement)\n    } finally {\n      submittingRef.current = false\n    }\n  }\n\n  return (\n    <form\n      {...props}\n      onInvalid={(event) =>\n        focusFirstInvalidAuthFormControl(event.currentTarget)\n      }\n      onInput={(event) => {\n        const target = event.target\n        const fieldName =\n          target instanceof HTMLInputElement ||\n          target instanceof HTMLSelectElement ||\n          target instanceof HTMLTextAreaElement\n            ? target.name\n            : \"\"\n        clearAuthFormFieldServerError(form, fieldName)\n        onInput?.(event)\n      }}\n      onSubmit={submit}\n    >\n      {children}\n    </form>\n  )\n}\n\ntype AuthFormTextFieldProps = Omit<\n  ComponentProps<typeof Input>,\n  \"name\" | \"onBlur\" | \"onChange\" | \"value\"\n> & {\n  description?: ReactNode\n  label: ReactNode\n}\n\nfunction AuthFormTextField({\n  description,\n  id,\n  label,\n  ...props\n}: AuthFormTextFieldProps) {\n  const field = useFieldContext<string>()\n  const form = useFormContext()\n  const isInvalid = isAuthFormFieldInvalid(field.state.meta)\n  const inputId = id ?? field.name\n\n  return (\n    <Field data-invalid={isInvalid}>\n      <FieldLabel htmlFor={inputId}>{label}</FieldLabel>\n      <Input\n        {...props}\n        aria-busy={field.state.meta.isValidating || undefined}\n        aria-invalid={isInvalid}\n        id={inputId}\n        name={field.name}\n        onBlur={field.handleBlur}\n        onChange={(event) => {\n          clearAuthFormFieldServerError(form, field.name)\n          field.handleChange(event.target.value)\n        }}\n        value={field.state.value}\n      />\n      {description ? <FieldDescription>{description}</FieldDescription> : null}\n      <AuthFormFieldError />\n    </Field>\n  )\n}\n\nfunction AuthFormSubmitButton({\n  children,\n  disabled,\n  isPending,\n  ...props\n}: ComponentProps<typeof Button> & { isPending?: boolean }) {\n  const form = useFormContext()\n\n  return (\n    <form.Subscribe\n      selector={(state) => [state.isSubmitting, state.isValidating] as const}\n    >\n      {([isSubmitting, isValidating]) => (\n        <Button\n          {...props}\n          aria-busy={isPending || isSubmitting || undefined}\n          aria-disabled={\n            disabled || isPending || isSubmitting || isValidating || undefined\n          }\n          disabled={disabled || isPending || isSubmitting || isValidating}\n          type=\"submit\"\n        >\n          {isPending || isSubmitting ? (\n            <Spinner data-icon=\"inline-start\" />\n          ) : null}\n          {children}\n        </Button>\n      )}\n    </form.Subscribe>\n  )\n}\n\ntype AuthFormAdditionalFieldProps = Omit<\n  AdditionalFieldProps,\n  \"errors\" | \"isInvalid\" | \"name\" | \"onBlur\" | \"onChange\" | \"value\"\n>\n\nfunction AuthFormAdditionalField(props: AuthFormAdditionalFieldProps) {\n  const field = useFieldContext<AdditionalFieldFormValue>()\n  const form = useFormContext()\n  const isInvalid = isAuthFormFieldInvalid(field.state.meta)\n\n  return (\n    <AdditionalField\n      {...props}\n      errors={\n        isInvalid ? getFormFieldErrors(field.state.meta.errors) : undefined\n      }\n      isInvalid={isInvalid}\n      name={field.name}\n      onBlur={field.handleBlur}\n      onChange={(value) => {\n        clearAuthFormFieldServerError(form, field.name)\n        field.handleChange(value)\n      }}\n      value={field.state.value}\n    />\n  )\n}\n\nexport const {\n  useAppForm: useAuthForm,\n  withFieldGroup: withAuthFieldGroup,\n  withForm: withAuthForm\n} = createFormHook({\n  fieldComponents: {\n    AuthFormAdditionalField,\n    AuthFormFieldError,\n    AuthFormTextField\n  },\n  fieldContext,\n  formComponents: {\n    AuthFormRoot,\n    AuthFormServerError,\n    AuthFormSubmitButton\n  },\n  formContext\n})\n\nexport function isAuthFormFieldInvalid({\n  isTouched,\n  isValid\n}: {\n  isTouched: boolean\n  isValid: boolean\n}) {\n  return isTouched && !isValid\n}\n\nexport function getAuthAdditionalFieldValidators(\n  field: AdditionalFieldConfig,\n  requiredMessage: string\n) {\n  return {\n    onChange: ({ value }: { value: AdditionalFieldFormValue }) =>\n      validateAdditionalFieldRequired(field, value, requiredMessage),\n    onChangeAsync: field.validate\n      ? ({ value }: { value: AdditionalFieldFormValue }) =>\n          validateAdditionalFieldValue(field, value)\n      : undefined,\n    onChangeAsyncDebounceMs: field.validate\n      ? (field.validateDebounceMs ??\n        DEFAULT_ADDITIONAL_FIELD_VALIDATION_DEBOUNCE_MS)\n      : undefined\n  }\n}\n",
      "type": "registry:component",
      "target": "@components/auth/auth-form.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/last-login-method/last-used-badge.tsx",
      "content": "\"use client\"\n\nimport { useLastLoginMethod } from \"@better-auth-ui/react\"\n\nimport { Badge } from \"@/components/ui/badge\"\n\nexport type LastUsedBadgeProps = {\n  /** Login method IDs that should display the indicator. */\n  method: string | string[]\n  /** Use the shorter label in constrained layouts. */\n  compact?: boolean\n  /** Float the compact indicator over the top-right edge of its container. */\n  floating?: boolean\n}\n\n/**\n * Displays an indicator when one of the supplied method IDs matches Better\n * Auth's stored last login method.\n */\nexport function LastUsedBadge({\n  method,\n  compact,\n  floating\n}: LastUsedBadgeProps) {\n  const { method: lastLoginMethod, localization } = useLastLoginMethod()\n  const methods = Array.isArray(method) ? method : [method]\n\n  if (!lastLoginMethod || !methods.includes(lastLoginMethod)) return null\n\n  return (\n    <Badge\n      className={\n        floating\n          ? \"pointer-events-none absolute top-0 right-0 z-10 translate-x-1/4 -translate-y-1/2 shadow-sm\"\n          : undefined\n      }\n      variant=\"secondary\"\n    >\n      {compact || floating ? localization.lastUsedShort : localization.lastUsed}\n    </Badge>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/last-login-method/last-used-badge.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/provider-button.tsx",
      "content": "\"use client\"\n\nimport {\n  type AuthSocialProvider,\n  type AuthView,\n  authMutationKeys,\n  getProviderId,\n  getProviderName,\n  type OAuthPopupAuthClient\n} from \"@better-auth-ui/core\"\nimport {\n  renderProviderIcon,\n  useAuth,\n  useFetchOptions,\n  useSignInOAuthPopup,\n  useSignInSocial\n} from \"@better-auth-ui/react\"\nimport { useIsMutating } from \"@tanstack/react-query\"\nimport type { ComponentProps } from \"react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { cn } from \"cn\"\nimport { LastUsedBadge } from \"./last-login-method/last-used-badge\"\n\nexport type ProviderButtonProps = {\n  provider: AuthSocialProvider\n  display?: \"full\" | \"name\" | \"icon\"\n  view?: AuthView\n} & Omit<ComponentProps<typeof Button>, \"onClick\" | \"children\" | \"disabled\">\n\n/**\n * Social provider sign-in button.\n *\n * @param provider - Provider to sign in with.\n * @param display - `\"full\"` (e.g. \"Continue with Google\"), `\"name\"` (just the provider name), or `\"icon\"` (icon only).\n */\nexport function ProviderButton({\n  provider,\n  display = \"full\",\n  view = \"signIn\",\n  variant = \"outline\",\n  className,\n  ...props\n}: ProviderButtonProps) {\n  const {\n    authClient,\n    baseURL,\n    localization,\n    navigate,\n    redirectTo,\n    socialSignInMode\n  } = useAuth()\n\n  const callbackURL = `${baseURL}${redirectTo}`\n  const { fetchOptions, resetFetchOptions } = useFetchOptions()\n\n  const { mutate: signInSocial, isPending: signInSocialPending } =\n    useSignInSocial(authClient, { onError: resetFetchOptions })\n  const { mutate: signInPopup, isPending: signInPopupPending } =\n    useSignInOAuthPopup(authClient as OAuthPopupAuthClient, {\n      onError: resetFetchOptions\n    })\n\n  const providerId = getProviderId(provider)\n  const providerIcon = renderProviderIcon(provider)\n\n  const signInMutating = useIsMutating({\n    mutationKey: authMutationKeys.signIn.all\n  })\n  const signUpMutating = useIsMutating({\n    mutationKey: authMutationKeys.signUp.all\n  })\n  const isPending = signInMutating + signUpMutating > 0\n\n  const handleSignIn = () => {\n    if (socialSignInMode === \"popup\") {\n      signInPopup(\n        {\n          provider: providerId,\n          callbackURL,\n          requestSignUp: view === \"signUp\"\n        },\n        { onSuccess: () => navigate({ to: redirectTo }) }\n      )\n      return\n    }\n\n    signInSocial({ provider: providerId, callbackURL, fetchOptions })\n  }\n\n  return (\n    <Button\n      type=\"button\"\n      variant={variant}\n      disabled={isPending}\n      onClick={handleSignIn}\n      className={cn(\"relative overflow-visible\", className)}\n      {...props}\n    >\n      {signInSocialPending || signInPopupPending ? <Spinner /> : providerIcon}\n\n      {display === \"full\"\n        ? localization.auth.continueWith.replace(\n            \"{{provider}}\",\n            getProviderName(provider)\n          )\n        : display === \"name\"\n          ? getProviderName(provider)\n          : null}\n\n      {display === \"icon\" && (\n        <span className=\"sr-only\">{getProviderName(provider)}</span>\n      )}\n\n      {view !== \"signUp\" && <LastUsedBadge method={providerId} floating />}\n    </Button>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/provider-button.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/provider-buttons.tsx",
      "content": "\"use client\"\n\nimport { type AuthView, getProviderId } from \"@better-auth-ui/core\"\nimport { useAuth } from \"@better-auth-ui/react\"\nimport { useMemo } from \"react\"\n\nimport { cn } from \"cn\"\nimport { ProviderButton } from \"./provider-button\"\n\nexport type ProviderButtonsProps = {\n  socialLayout?: SocialLayout\n  view?: AuthView\n}\n\nexport type SocialLayout = \"auto\" | \"horizontal\" | \"vertical\" | \"grid\"\n\n/**\n * Render sign-in buttons for configured social providers. Each button owns its own sign-in mutation\n * and reads the shared sign-in pending state from React Query.\n *\n * @param socialLayout - Preferred layout for the provider buttons; `\"auto\"` chooses based on the number of providers.\n */\nexport function ProviderButtons({\n  socialLayout = \"auto\",\n  view = \"signIn\"\n}: ProviderButtonsProps) {\n  const { socialProviders } = useAuth()\n\n  const resolvedSocialLayout = useMemo(() => {\n    if (socialLayout === \"auto\") {\n      if (socialProviders?.length && socialProviders.length >= 4) {\n        return \"horizontal\"\n      }\n\n      return \"vertical\"\n    }\n\n    return socialLayout\n  }, [socialLayout, socialProviders?.length])\n\n  return (\n    <div\n      className={cn(\n        \"gap-3\",\n        resolvedSocialLayout === \"grid\" && \"grid grid-cols-2\",\n        resolvedSocialLayout === \"vertical\" && \"flex flex-col\",\n        resolvedSocialLayout === \"horizontal\" && \"flex flex-row flex-wrap\"\n      )}\n    >\n      {socialProviders?.map((provider) => (\n        <ProviderButton\n          key={getProviderId(provider)}\n          provider={provider}\n          view={view}\n          display={\n            resolvedSocialLayout === \"vertical\"\n              ? \"full\"\n              : resolvedSocialLayout === \"grid\"\n                ? \"name\"\n                : \"icon\"\n          }\n          className={cn(resolvedSocialLayout === \"horizontal\" && \"flex-1\")}\n        />\n      ))}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/provider-buttons.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/reauthentication.tsx",
      "content": "\"use client\"\n\nimport {\n  getReauthenticationSignInURL,\n  isReauthenticationSignInURL\n} from \"@better-auth-ui/core\"\nimport { useAuth, useSignOut } from \"@better-auth-ui/react\"\nimport { useSyncExternalStore } from \"react\"\nimport { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\"\nimport { Button } from \"@/components/ui/button\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { cn } from \"cn\"\n\nconst subscribeToLocation = () => () => undefined\n\nfunction useIsReauthenticationSignIn() {\n  return useSyncExternalStore(\n    subscribeToLocation,\n    () => isReauthenticationSignInURL(new URL(window.location.href)),\n    () => false\n  )\n}\n\nexport type ReauthenticationActionProps = {\n  className?: string\n  showTitle?: boolean\n}\n\nexport function ReauthenticationAction({\n  className,\n  showTitle = true\n}: ReauthenticationActionProps) {\n  const auth = useAuth()\n  const signOut = useSignOut(auth.authClient)\n\n  const handleReauthentication = () => {\n    const signInURL = getReauthenticationSignInURL(\n      new URL(window.location.href),\n      `${auth.basePaths.auth}/${auth.viewPaths.auth.signIn}`\n    )\n    signOut.mutate(undefined, {\n      onSuccess: () => auth.navigate({ to: signInURL })\n    })\n  }\n\n  return (\n    <div className={cn(\"flex flex-col items-start gap-3 p-4\", className)}>\n      <div className=\"flex flex-col gap-1\">\n        {showTitle ? (\n          <h3 className=\"text-sm font-medium\">\n            {auth.localization.settings.reauthenticationTitle}\n          </h3>\n        ) : null}\n        <p className=\"text-sm text-muted-foreground\">\n          {auth.localization.settings.reauthenticationDescription}\n        </p>\n      </div>\n      <Button\n        disabled={signOut.isPending}\n        onClick={handleReauthentication}\n        size=\"sm\"\n      >\n        {signOut.isPending ? <Spinner data-icon=\"inline-start\" /> : null}\n        {auth.localization.settings.reauthenticationAction}\n      </Button>\n    </div>\n  )\n}\n\nexport function ReauthenticationNotice() {\n  const auth = useAuth()\n  const isReauthenticationSignIn = useIsReauthenticationSignIn()\n  if (!isReauthenticationSignIn) return null\n\n  return (\n    <Alert className=\"mx-4 w-auto group-data-[size=sm]/card:mx-3\">\n      <AlertTitle>\n        {auth.localization.settings.reauthenticationTitle}\n      </AlertTitle>\n      <AlertDescription>\n        {auth.localization.settings.reauthenticationDescription}\n      </AlertDescription>\n    </Alert>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/reauthentication.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/username/sign-in-username.tsx",
      "content": "\"use client\"\n\nimport { authMutationKeys, validateStringLength } from \"@better-auth-ui/core\"\nimport {\n  isPasskeyAutoFillEnabled,\n  withPasskeyAutoFill\n} from \"@better-auth-ui/core/plugins/passkey\"\nimport type { UsernameAuthClient } from \"@better-auth-ui/core/plugins/username\"\nimport {\n  AuthPrompts,\n  useAuth,\n  useAuthPlugin,\n  useFetchOptions,\n  useSignInEmail\n} from \"@better-auth-ui/react\"\nimport { useSignInUsername } from \"@better-auth-ui/react/plugins/username\"\nimport { useIsMutating } from \"@tanstack/react-query\"\nimport { Eye, EyeOff } from \"lucide-react\"\nimport { useState } from \"react\"\nimport {\n  ProviderButtons,\n  type SocialLayout\n} from \"@/components/auth/provider-buttons\"\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport {\n  Field,\n  FieldDescription,\n  FieldGroup,\n  FieldLabel,\n  FieldSeparator\n} from \"@/components/ui/field\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupInput\n} from \"@/components/ui/input-group\"\nimport { useSignInContinuation } from \"@/lib/auth/use-sign-in-continuation\"\nimport { usernamePlugin } from \"@/lib/auth/username-plugin\"\nimport { cn } from \"cn\"\nimport { isAuthFormFieldInvalid, useAuthForm } from \"../auth-form\"\nimport { LastUsedBadge } from \"../last-login-method/last-used-badge\"\nimport { ReauthenticationNotice } from \"../reauthentication\"\n\nexport type SignInUsernameProps = {\n  className?: string\n  socialLayout?: SocialLayout\n  socialPosition?: \"top\" | \"bottom\"\n}\n\nfunction isEmail(value: string): boolean {\n  return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value)\n}\n\n/**\n * Render the username-based sign-in form. Identical to the built-in `<SignIn>`\n * design but routes non-email inputs through `signInUsername` instead of\n * `signInEmail`.\n */\nexport function SignInUsername({\n  className,\n  socialLayout,\n  socialPosition = \"bottom\"\n}: SignInUsernameProps) {\n  const {\n    authClient,\n    basePaths,\n    emailAndPassword,\n    localization,\n    plugins,\n    socialProviders,\n    viewPaths,\n    navigate,\n    Link\n  } = useAuth<UsernameAuthClient>()\n\n  const { fetchOptions, resetFetchOptions } = useFetchOptions()\n  const continueSignIn = useSignInContinuation()\n\n  const { localization: usernameLocalization } = useAuthPlugin(usernamePlugin)\n\n  const { mutateAsync: signInEmail, isPending: isSignInEmailPending } =\n    useSignInEmail(authClient, {\n      onError: (error, { email }) => {\n        form.setFieldValue(\"password\", \"\")\n\n        if (error.error?.code === \"EMAIL_NOT_VERIFIED\") {\n          sessionStorage.setItem(\"better-auth-ui.verify-email\", email)\n          navigate({\n            to: `${basePaths.auth}/${viewPaths.auth.verifyEmail}`\n          })\n        }\n\n        resetFetchOptions()\n      },\n      onSuccess: (data) => {\n        sessionStorage.removeItem(\"better-auth-ui.verify-email\")\n        continueSignIn(data)\n      }\n    })\n\n  const { mutateAsync: signInUsername, isPending: isSignInUsernamePending } =\n    useSignInUsername(authClient, {\n      onError: (error) => {\n        form.setFieldValue(\"password\", \"\")\n\n        if (error.error?.code === \"EMAIL_NOT_VERIFIED\") {\n          sessionStorage.removeItem(\"better-auth-ui.verify-email\")\n\n          navigate({\n            to: `${basePaths.auth}/${viewPaths.auth.verifyEmail}`\n          })\n        }\n\n        resetFetchOptions()\n      },\n      onSuccess: (data) => {\n        sessionStorage.removeItem(\"better-auth-ui.verify-email\")\n        continueSignIn(data)\n      }\n    })\n\n  const signInMutating = useIsMutating({\n    mutationKey: authMutationKeys.signIn.all\n  })\n  const signUpMutating = useIsMutating({\n    mutationKey: authMutationKeys.signUp.all\n  })\n  const isPending = signInMutating + signUpMutating > 0\n  const isSignInPending = isSignInEmailPending || isSignInUsernamePending\n\n  const Captcha = plugins.find(\n    (plugin) => plugin.captchaComponent\n  )?.captchaComponent\n\n  const passkeyAutoFill = isPasskeyAutoFillEnabled(plugins)\n\n  const [isPasswordVisible, setIsPasswordVisible] = useState(false)\n  const form = useAuthForm({\n    defaultValues: { identifier: \"\", password: \"\", rememberMe: false },\n    onSubmit: async ({ value }) => {\n      if (isEmail(value.identifier)) {\n        await signInEmail({\n          email: value.identifier,\n          password: value.password,\n          ...(emailAndPassword?.rememberMe\n            ? { rememberMe: value.rememberMe }\n            : {}),\n          fetchOptions\n        })\n      } else {\n        await signInUsername({\n          username: value.identifier,\n          password: value.password,\n          ...(emailAndPassword?.rememberMe\n            ? { rememberMe: value.rememberMe }\n            : {}),\n          fetchOptions\n        })\n      }\n    }\n  })\n\n  const showSeparator =\n    emailAndPassword?.enabled && socialProviders && socialProviders.length > 0\n\n  return (\n    <Card className={cn(\"w-full max-w-sm\", className)}>\n      <AuthPrompts view=\"signIn\" />\n      <ReauthenticationNotice />\n      <CardHeader>\n        <CardTitle className=\"text-xl font-semibold\">\n          {localization.auth.signIn}\n        </CardTitle>\n      </CardHeader>\n\n      <CardContent>\n        <div className=\"flex flex-col gap-6\">\n          {socialPosition === \"top\" && (\n            <>\n              {socialProviders && socialProviders.length > 0 && (\n                <ProviderButtons socialLayout={socialLayout} view=\"signIn\" />\n              )}\n\n              {showSeparator && (\n                <FieldSeparator className=\"*:data-[slot=field-separator-content]:bg-card m-0 text-xs flex items-center\">\n                  {localization.auth.or}\n                </FieldSeparator>\n              )}\n            </>\n          )}\n\n          {emailAndPassword?.enabled && (\n            <form.AppForm>\n              <form.AuthFormRoot>\n                <FieldGroup>\n                  <form.AppField\n                    name=\"identifier\"\n                    validators={{\n                      onChange: ({ value }) =>\n                        validateStringLength(value, {\n                          requiredMessage: localization.auth.fieldRequired,\n                          trim: true\n                        })\n                    }}\n                  >\n                    {(field) => {\n                      const isInvalid = isAuthFormFieldInvalid(field.state.meta)\n                      return (\n                        <Field data-invalid={isInvalid}>\n                          <FieldLabel htmlFor=\"email\">\n                            {usernameLocalization.username}\n                          </FieldLabel>\n\n                          <Input\n                            id=\"email\"\n                            name={field.name}\n                            type=\"text\"\n                            autoComplete={withPasskeyAutoFill(\n                              \"username\",\n                              passkeyAutoFill\n                            )}\n                            placeholder={\n                              usernameLocalization.usernameOrEmailPlaceholder\n                            }\n                            required\n                            disabled={isPending}\n                            value={field.state.value}\n                            onBlur={field.handleBlur}\n                            onChange={(event) =>\n                              field.handleChange(event.target.value)\n                            }\n                            aria-invalid={isInvalid}\n                          />\n                          <field.AuthFormFieldError />\n                        </Field>\n                      )\n                    }}\n                  </form.AppField>\n\n                  <form.AppField\n                    name=\"password\"\n                    validators={{\n                      onChange: ({ value }) =>\n                        validateStringLength(value, {\n                          maxLength: emailAndPassword?.maxPasswordLength,\n                          maxLengthMessage: localization.auth.tooLong.replace(\n                            \"{{max}}\",\n                            String(emailAndPassword?.maxPasswordLength)\n                          ),\n                          minLength: emailAndPassword?.minPasswordLength,\n                          minLengthMessage: localization.auth.tooShort.replace(\n                            \"{{min}}\",\n                            String(emailAndPassword?.minPasswordLength)\n                          ),\n                          requiredMessage: localization.auth.fieldRequired\n                        })\n                    }}\n                  >\n                    {(field) => {\n                      const isInvalid = isAuthFormFieldInvalid(field.state.meta)\n                      return (\n                        <Field data-invalid={isInvalid}>\n                          <FieldLabel htmlFor=\"password\">\n                            {localization.auth.password}\n                          </FieldLabel>\n\n                          <InputGroup>\n                            <InputGroupInput\n                              id=\"password\"\n                              name={field.name}\n                              type={isPasswordVisible ? \"text\" : \"password\"}\n                              autoComplete={withPasskeyAutoFill(\n                                \"current-password\",\n                                passkeyAutoFill\n                              )}\n                              value={field.state.value}\n                              onBlur={field.handleBlur}\n                              onChange={(event) =>\n                                field.handleChange(event.target.value)\n                              }\n                              placeholder={\n                                localization.auth.passwordPlaceholder\n                              }\n                              required\n                              minLength={emailAndPassword?.minPasswordLength}\n                              maxLength={emailAndPassword?.maxPasswordLength}\n                              disabled={isPending}\n                              aria-invalid={isInvalid}\n                            />\n\n                            <InputGroupAddon align=\"inline-end\">\n                              <InputGroupButton\n                                size=\"icon-xs\"\n                                aria-label={\n                                  isPasswordVisible\n                                    ? localization.auth.hidePassword\n                                    : localization.auth.showPassword\n                                }\n                                title={\n                                  isPasswordVisible\n                                    ? localization.auth.hidePassword\n                                    : localization.auth.showPassword\n                                }\n                                onClick={() => {\n                                  setIsPasswordVisible((visible) => !visible)\n                                }}\n                              >\n                                {isPasswordVisible ? <EyeOff /> : <Eye />}\n                              </InputGroupButton>\n                            </InputGroupAddon>\n                          </InputGroup>\n\n                          <field.AuthFormFieldError />\n                        </Field>\n                      )\n                    }}\n                  </form.AppField>\n\n                  {emailAndPassword.rememberMe && (\n                    <form.AppField name=\"rememberMe\">\n                      {(field) => (\n                        <Field className=\"my-1\">\n                          <div className=\"flex items-center gap-3\">\n                            <Checkbox\n                              id=\"rememberMe\"\n                              name={field.name}\n                              checked={field.state.value}\n                              disabled={isPending}\n                              onCheckedChange={(checked) =>\n                                field.handleChange(checked === true)\n                              }\n                            />\n\n                            <FieldLabel\n                              htmlFor=\"rememberMe\"\n                              className=\"cursor-pointer text-sm font-normal\"\n                            >\n                              {localization.auth.rememberMe}\n                            </FieldLabel>\n                          </div>\n                        </Field>\n                      )}\n                    </form.AppField>\n                  )}\n\n                  {Captcha && (\n                    <div className=\"flex justify-center\">{Captcha}</div>\n                  )}\n\n                  <form.AuthFormServerError />\n\n                  <div className=\"flex flex-col gap-3\">\n                    <form.AuthFormSubmitButton\n                      isPending={isSignInPending}\n                      className=\"relative overflow-visible\"\n                      disabled={isPending}\n                    >\n                      {localization.auth.signIn}\n\n                      <LastUsedBadge method={[\"email\", \"username\"]} floating />\n                    </form.AuthFormSubmitButton>\n\n                    {plugins.flatMap((plugin) =>\n                      (plugin.authButtons ?? []).map((AuthButton, index) => (\n                        <AuthButton\n                          key={`${plugin.id}-${index.toString()}`}\n                          view=\"signIn\"\n                        />\n                      ))\n                    )}\n                  </div>\n                </FieldGroup>\n              </form.AuthFormRoot>\n            </form.AppForm>\n          )}\n\n          {socialPosition === \"bottom\" && (\n            <>\n              {showSeparator && (\n                <FieldSeparator className=\"*:data-[slot=field-separator-content]:bg-card text-xs flex items-center\">\n                  {localization.auth.or}\n                </FieldSeparator>\n              )}\n\n              {socialProviders && socialProviders.length > 0 && (\n                <ProviderButtons socialLayout={socialLayout} view=\"signIn\" />\n              )}\n            </>\n          )}\n        </div>\n\n        <div className=\"flex flex-col gap-3 items-center w-full mt-4\">\n          {emailAndPassword?.enabled && emailAndPassword?.forgotPassword && (\n            <Link\n              href={`${basePaths.auth}/${viewPaths.auth.forgotPassword}`}\n              className=\"self-center text-sm underline-offset-4 hover:underline\"\n            >\n              {localization.auth.forgotPasswordLink}\n            </Link>\n          )}\n\n          {emailAndPassword?.enabled && (\n            <FieldDescription className=\"text-center\">\n              {localization.auth.needToCreateAnAccount}{\" \"}\n              <Link\n                href={`${basePaths.auth}/${viewPaths.auth.signUp}`}\n                className=\"underline underline-offset-4\"\n              >\n                {localization.auth.signUp}\n              </Link>\n            </FieldDescription>\n          )}\n        </div>\n      </CardContent>\n    </Card>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/username/sign-in-username.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/components/auth/username/username-field.tsx",
      "content": "\"use client\"\n\nimport { getFormFieldErrors } from \"@better-auth-ui/core\"\nimport type { UsernameAuthClient } from \"@better-auth-ui/core/plugins/username\"\nimport { useAuth, useAuthPlugin } from \"@better-auth-ui/react\"\nimport { useIsUsernameAvailable } from \"@better-auth-ui/react/plugins/username\"\nimport { useDebouncer } from \"@tanstack/react-pacer\"\nimport { Check, X } from \"lucide-react\"\nimport { useState } from \"react\"\nimport type { AdditionalFieldProps } from \"@/components/auth/additional-field\"\nimport { Field, FieldError, FieldLabel } from \"@/components/ui/field\"\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupInput\n} from \"@/components/ui/input-group\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { usernamePlugin } from \"@/lib/auth/username-plugin\"\n\n/**\n * Renderer for the `username` additional field. Owns availability checking,\n * length limits, and visual indicators. `isInvalid` reflects only browser\n * validation (minLength, required, etc.) — availability feedback is shown\n * via the icon and `aria-label` without affecting the field's invalid state.\n */\nexport function UsernameField({\n  name,\n  field,\n  value,\n  onBlur,\n  onChange,\n  isInvalid,\n  errors,\n  isPending\n}: AdditionalFieldProps) {\n  const { authClient, localization: authLocalization } =\n    useAuth<UsernameAuthClient>()\n  const {\n    localization,\n    minUsernameLength,\n    maxUsernameLength,\n    isUsernameAvailable: checkAvailability,\n    usernamePrefix\n  } = useAuthPlugin(usernamePlugin)\n\n  const currentUsername = String(field.defaultValue ?? \"\")\n  const username = typeof value === \"string\" ? value : \"\"\n  const [nativeError, setNativeError] = useState<string>()\n  const fieldErrors = getFormFieldErrors(errors ?? [])\n\n  const {\n    mutate: requestAvailability,\n    data: availability,\n    error: availabilityError,\n    reset: resetAvailability\n  } = useIsUsernameAvailable(authClient, {\n    onError: () => {}\n  })\n\n  const debouncer = useDebouncer(\n    (next: string) => {\n      const trimmed = next.trim()\n      if (!trimmed || trimmed === currentUsername) {\n        resetAvailability()\n        return\n      }\n\n      requestAvailability({ username: trimmed })\n    },\n    { wait: 500 }\n  )\n\n  function handleChange(next: string) {\n    onChange(next || null)\n    setNativeError(undefined)\n    resetAvailability()\n\n    if (checkAvailability) {\n      debouncer.maybeExecute(next)\n    }\n  }\n\n  const isCheckingAvailability =\n    !!checkAvailability &&\n    !!username.trim() &&\n    username.trim() !== currentUsername\n\n  return (\n    <Field data-invalid={isInvalid || !!nativeError}>\n      <FieldLabel htmlFor={name}>{field.label}</FieldLabel>\n\n      <InputGroup>\n        {usernamePrefix && (\n          <InputGroupAddon align=\"inline-start\">\n            {usernamePrefix}\n          </InputGroupAddon>\n        )}\n\n        <InputGroupInput\n          id={name}\n          name={name}\n          type=\"text\"\n          autoComplete=\"username\"\n          minLength={minUsernameLength}\n          maxLength={maxUsernameLength}\n          disabled={isPending}\n          required={field.required}\n          readOnly={field.readOnly}\n          value={username}\n          onBlur={onBlur}\n          onChange={(e) => handleChange(e.target.value)}\n          onInvalid={(e) => {\n            e.preventDefault()\n            const el = e.target as HTMLInputElement\n            const msg = el.validity.valueMissing\n              ? authLocalization.auth.fieldRequired\n              : el.validity.tooShort\n                ? authLocalization.auth.tooShort.replace(\n                    \"{{min}}\",\n                    String(minUsernameLength)\n                  )\n                : authLocalization.auth.tooLong.replace(\n                    \"{{max}}\",\n                    String(maxUsernameLength)\n                  )\n            setNativeError(msg)\n          }}\n          aria-invalid={isInvalid || !!nativeError}\n          placeholder={field.placeholder}\n        />\n\n        {isCheckingAvailability && (\n          <InputGroupAddon\n            align=\"inline-end\"\n            aria-label={\n              availability?.available\n                ? localization.usernameAvailable\n                : availability?.available === false\n                  ? localization.usernameTaken\n                  : undefined\n            }\n          >\n            {availability?.available ? (\n              <Check className=\"size-4 text-foreground\" />\n            ) : availabilityError || availability?.available === false ? (\n              <X className=\"size-4 text-destructive\" />\n            ) : (\n              <Spinner />\n            )}\n          </InputGroupAddon>\n        )}\n      </InputGroup>\n\n      <FieldError errors={fieldErrors}>{nativeError}</FieldError>\n    </Field>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/username/username-field.tsx"
    },
    {
      "path": "examples/start-shadcn-example/src/lib/auth/auth-plugin.ts",
      "content": "import type {\n  AuthPluginComponents,\n  AuthPlugin as AuthPluginPrimitive\n} from \"@better-auth-ui/react\"\n\ndeclare module \"@better-auth-ui/core\" {\n  /** Widens `useAuth().plugins` to the shadcn-typed `AuthPlugin`. */\n  interface AuthPluginRegister {\n    shadcn: AuthPlugin\n  }\n}\n\n/** Props the shadcn `<Auth>` router spreads onto plugin-contributed auth views. */\nexport type AuthViewProps = {\n  className?: string\n  socialLayout?: \"auto\" | \"horizontal\" | \"vertical\" | \"grid\"\n  socialPosition?: \"top\" | \"bottom\"\n}\n\n/** Props the shadcn `<Settings>` router spreads onto plugin-contributed settings views. */\nexport type SettingsViewProps = {\n  className?: string\n}\n\n/** Shadcn plugin type. Plugin authors import this from `@/lib/auth/auth-plugin`. */\nexport type AuthPlugin = AuthPluginPrimitive<\n  AuthPluginComponents,\n  AuthViewProps,\n  SettingsViewProps\n>\n",
      "type": "registry:lib",
      "target": "@lib/auth/auth-plugin.ts"
    },
    {
      "path": "examples/start-shadcn-example/src/lib/auth/two-factor-methods.ts",
      "content": "/**\n * Second-factor methods a user can complete the challenge with.\n *\n * `totp` and `otp` come from the server's `twoFactorMethods` list. Backup\n * codes are always available when the server has them enabled, so they are\n * never part of that list.\n */\nexport type TwoFactorMethod = \"totp\" | \"otp\"\n\nconst TWO_FACTOR_METHODS: TwoFactorMethod[] = [\"totp\", \"otp\"]\n\n/** Auth plugin id used by Better Auth UI's two-factor integration. */\nexport const TWO_FACTOR_PLUGIN_ID = \"twoFactor\"\n\n/**\n * `sessionStorage` key holding the methods reported by the sign-in response.\n *\n * Only the non-sensitive method names are stored, never a code, token, or the\n * two-factor cookie, which stays HTTP-only.\n */\nexport const TWO_FACTOR_METHODS_STORAGE_KEY =\n  \"better-auth-ui.two-factor-methods\"\n\ntype TwoFactorRedirect = {\n  twoFactorRedirect: true\n  twoFactorMethods?: unknown\n}\n\n/** Detect the redirect payload Better Auth returns before a second factor. */\nexport function isTwoFactorRedirect(data: unknown): data is TwoFactorRedirect {\n  return (\n    typeof data === \"object\" &&\n    data !== null &&\n    (data as { twoFactorRedirect?: unknown }).twoFactorRedirect === true\n  )\n}\n\n/** Narrow arbitrary method names to the challenge views this UI supports. */\nexport function parseTwoFactorMethods(methods?: unknown): TwoFactorMethod[] {\n  if (!Array.isArray(methods)) return []\n\n  return TWO_FACTOR_METHODS.filter((method) => methods.includes(method))\n}\n\n/** Persist the enabled method names without blocking sign-in on storage errors. */\nexport function storeTwoFactorMethods(methods?: unknown) {\n  if (typeof sessionStorage === \"undefined\") return\n\n  try {\n    sessionStorage.setItem(\n      TWO_FACTOR_METHODS_STORAGE_KEY,\n      JSON.stringify(parseTwoFactorMethods(methods))\n    )\n  } catch {\n    // The challenge falls back to every method when storage is unavailable.\n  }\n}\n\n/** Read the stored methods, falling back to every supported challenge. */\nexport function readTwoFactorMethods(): TwoFactorMethod[] {\n  if (typeof sessionStorage === \"undefined\") return TWO_FACTOR_METHODS\n\n  try {\n    const stored = sessionStorage.getItem(TWO_FACTOR_METHODS_STORAGE_KEY)\n    if (!stored) return TWO_FACTOR_METHODS\n\n    const methods = parseTwoFactorMethods(JSON.parse(stored))\n    return methods.length ? methods : TWO_FACTOR_METHODS\n  } catch {\n    return TWO_FACTOR_METHODS\n  }\n}\n\n/** Clear stored method hints after the challenge finishes or is abandoned. */\nexport function clearTwoFactorMethods() {\n  if (typeof sessionStorage === \"undefined\") return\n\n  try {\n    sessionStorage.removeItem(TWO_FACTOR_METHODS_STORAGE_KEY)\n  } catch {\n    // Stale method hints are harmless and must not block navigation.\n  }\n}\n",
      "type": "registry:lib",
      "target": "@lib/auth/two-factor-methods.ts"
    },
    {
      "path": "examples/start-shadcn-example/src/lib/auth/use-sign-in-continuation.ts",
      "content": "\"use client\"\n\nimport { useAuth } from \"@better-auth-ui/react\"\nimport { useCallback } from \"react\"\nimport {\n  isTwoFactorRedirect,\n  storeTwoFactorMethods,\n  TWO_FACTOR_PLUGIN_ID\n} from \"./two-factor-methods\"\n\n/**\n * Resolve what happens after a sign-in request succeeds.\n *\n * Better Auth withholds the session when a second factor is required and\n * answers with `{ twoFactorRedirect: true, twoFactorMethods }` instead, so no\n * sign-in strategy may navigate to `redirectTo` unconditionally. This hook is\n * the single place that decision lives — every password-based form calls it\n * from `onSuccess`.\n *\n * The enabled methods are stashed in session storage (names only, never a\n * code or token) and `redirectTo` rides along in the query string so the\n * challenge view can finish the original navigation.\n *\n * The two-factor plugin is looked up by its stable id, so sign-in forms stay\n * installable without either the two-factor components or a matching release\n * of `@better-auth-ui/core`.\n *\n * @returns A callback taking the resolved data of a sign-in mutation.\n */\nexport function useSignInContinuation() {\n  const { basePaths, navigate, plugins, redirectTo } = useAuth()\n\n  const twoFactorPath = plugins.find(\n    (plugin) => plugin.id === TWO_FACTOR_PLUGIN_ID\n  )?.viewPaths?.auth?.twoFactor\n\n  return useCallback(\n    (data: unknown) => {\n      if (twoFactorPath && isTwoFactorRedirect(data)) {\n        storeTwoFactorMethods(data.twoFactorMethods)\n\n        navigate({\n          to: `${basePaths.auth}/${twoFactorPath}?redirectTo=${encodeURIComponent(redirectTo)}`\n        })\n        return\n      }\n\n      navigate({ to: redirectTo })\n    },\n    [basePaths.auth, navigate, redirectTo, twoFactorPath]\n  )\n}\n",
      "type": "registry:lib",
      "target": "@lib/auth/use-sign-in-continuation.ts"
    },
    {
      "path": "examples/start-shadcn-example/src/lib/auth/username-plugin.ts",
      "content": "import { createAuthPlugin } from \"@better-auth-ui/core\"\nimport {\n  usernamePlugin as coreUsernamePlugin,\n  type UsernamePluginOptions\n} from \"@better-auth-ui/core/plugins/username\"\n\nimport { SignInUsername } from \"@/components/auth/username/sign-in-username\"\nimport { UsernameField } from \"@/components/auth/username/username-field\"\n\nexport const usernamePlugin = createAuthPlugin(\n  coreUsernamePlugin.id,\n  (options: UsernamePluginOptions = {}) => {\n    const core = coreUsernamePlugin(options)\n\n    return {\n      ...core,\n      additionalFields: core.additionalFields?.map((field) =>\n        field.name === \"username\"\n          ? {\n              ...field,\n              render: UsernameField\n            }\n          : field\n      ),\n      views: {\n        auth: { signIn: SignInUsername }\n      }\n    }\n  }\n)\n",
      "type": "registry:lib",
      "target": "@lib/auth/username-plugin.ts"
    }
  ],
  "type": "registry:component"
}