shadimage
quiz-ui
componentquiz-email-inputsource: ok · hit

QuizEmailInput

Email capture input with basic format validation.

npx shadcn add "https://quiz-ui-phi.vercel.app/r/quiz-email-input.json"
free

No selection tags yet — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
ui-primitives@radix-ui/react-label

Registry dependencies

@quiz-ui/quiz-core@quiz-ui/quiz-cn

Files1 · source cached 10/8/2026, 6:50:36 PM

components/ui/quiz/quiz-email-input.tsx
"use client";

import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { useQuizAnswers } from "./core";

const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

export interface QuizEmailInputProps
  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "onChange" | "type"> {
  label?: string;
  invalidMessage?: string;
  wrapperClassName?: string;
  labelClassName?: string;
  /** Applied on top of `className` when the field is touched and invalid. */
  invalidClassName?: string;
  errorMessageClassName?: string;
}

export const QuizEmailInput = React.forwardRef<HTMLInputElement, QuizEmailInputProps>(
  (
    {
      label,
      invalidMessage = "Enter a valid email address.",
      wrapperClassName,
      labelClassName,
      invalidClassName,
      errorMessageClassName,
      className,
      id,
      ...props
    },
    ref
  ) => {
    const { value, setValue } = useQuizAnswers();
    const [touched, setTouched] = React.useState(false);
    const inputId = id ?? React.useId();
    const stringValue = typeof value === "string" ? value : "";
    const isInvalid = touched && stringValue.length > 0 && !EMAIL_PATTERN.test(stringValue);

    return (
      <div className={wrapperClassName}>
        {label ? (
          <LabelPrimitive.Root htmlFor={inputId} className={labelClassName}>
            {label}
          </LabelPrimitive.Root>
        ) : null}
        <input
          ref={ref}
          id={inputId}
          type="email"
          inputMode="email"
          autoComplete="email"
          aria-invalid={isInvalid}
          className={isInvalid && invalidClassName ? `${className ?? ""} ${invalidClassName}` : className}
          value={stringValue}
          onChange={(e) => setValue(e.target.value)}
          onBlur={() => setTouched(true)}
          {...props}
        />
        {isInvalid ? <span className={errorMessageClassName}>{invalidMessage}</span> : null}
      </div>
    );
  }
);
QuizEmailInput.displayName = "QuizEmailInput";

More from quiz-ui

Browse all components in this library →