Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Forms

Field

Forms

Field React component for Tailwind CSS v4

CSV ImportForm Wizard
Live props
Field
label
description
error
required
optional
hideLabel
invalid
disabled
readOnly
Content
optionalLabel
Appearance
size
Layout
orientation
labelWidth160px
Behavior
countFollows typing
maxLengthNo counter
labelBehavior
labelAs
Code
import { Field } from "@/components/ui/field"

<Field />
  1. Home
  2. Components
  3. Forms
  4. Field
Free

Field React component for Tailwind CSS v4

v1.0.1·Updated in 1.7.0

A React form field wrapper every control shares: label, hint, animated error and counter, fieldsets, and the box recipe all inputs use.

Click or tap a label and its control takes focus; a click on the box's padding, an icon or a suffix like USD also lands in the input without moving the caret. Tab moves from field to field and the whole box takes a soft ring; Enter in a field submits the form like a native one. Press Continue with a title under 10 characters and the error slides in under the field while the rest of the form glides down; type again and it slides away, and a hint swaps for the error with a crossfade instead of a jump. With a limit set, the counter turns amber at 90% and red at the limit, and screen readers hear how many characters are left. A read-only field keeps its text selectable but ignores typing, a disabled one dims with its label. Horizontal fields put the label beside the control once the field is 448px wide and stack it on top below that, so on a phone every label sits above its field and every input is 16px so iOS never zooms.

Field is a free React component in the React form components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add field, together with Button, Motion presets and UIProvider and the npm packages @radix-ui/react-slot, motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add field

Usage

Import Field from @/components/ui/field.

import type { ComponentProps } from "react"
import { FIELD_INPUT, Field, FieldAddon, FieldBox, FieldControl, useFieldControl } from "@/components/ui/field"

export function PriceField({ error }: { error?: string }) {
  return (
    <Field label="Price" description="The final price, tax included." error={error} required>
      <FieldBox>
        <FieldControl>
          <input name="price" inputMode="decimal" placeholder="0" autoComplete="off" className={FIELD_INPUT} />
        </FieldControl>
        <FieldAddon>USD</FieldAddon>
      </FieldBox>
    </Field>
  )
}

// your own control: one hook, and it works alone or inside a <Field>
export function PlainInput({ label, error, ...props }: ComponentProps<"input"> & { label?: string; error?: string }) {
  const field = useFieldControl({ label, error })
  return field.wrap(
    <FieldBox invalid={field.invalid} disabled={field.disabled}>
      <input {...props} {...field.controlProps} className={FIELD_INPUT} />
    </FieldBox>,
  )
}

Props

  • labeldefault ""
    ReactNode

    The label, linked to the control with htmlFor (or aria-labelledby for groups). Leave it empty for a bare control.

  • descriptiondefault ""
    ReactNode

    Helper text under the control, linked with aria-describedby. The error takes its place while it shows.

  • errordefault ""
    ReactNode

    Replaces the description with an alert icon and the text, slides in and marks the field invalid. "", false and null are no error; true marks it invalid without text (prefer invalid for that).

  • requireddefault false
    boolean

    Shows the required mark after the label (hidden from screen readers) and tells the control to set required.

  • optionaldefault false
    boolean

    Shows optionalLabel after the label, for forms that mark the few optional fields instead of the many required ones.

  • optionalLabeldefault "Optional"
    string

    The word after an optional label.

  • requiredIndicatordefault "*"
    ReactNode

    The mark after a required label.

  • labelAction
    ReactNode

    A link or small button at the end of the label row, like Forgot your password?. It sits outside the <label> and gets a 44px hit area on touch.

  • hideLabeldefault false
    boolean

    Keeps the label for screen readers only, e.g. a search field whose icon already says what it is.

  • invaliddefault false
    boolean

    The red ring and aria-invalid without a message, for forms that show errors elsewhere. Passed to the control through context.

  • disableddefault false
    boolean

    Dims the label and, through context, disables the control. A disabled FieldGroup disables every field inside.

  • readOnlydefault false
    boolean

    The value stays visible and selectable but cannot change; the box drops its hover tint. Passed to the control through context.

  • sizedefault "md"
    "sm" | "md" | "lg"

    Label size (13 / 14 / 15px) and the box height horizontal labels align to. Controls take their own size; the demo passes the same to every box.

  • orientationdefault "vertical"
    "vertical" | "horizontal"

    horizontal puts the label in a column beside the control once the field itself is 448px wide (a container query, not the viewport) and stacks below that. Inherits from a FieldGroup.

  • labelWidthdefault 160
    number

    Width of the label column in horizontal fields, in px. Inherits from a FieldGroup.

  • countdefault 0
    number

    The current length, for the counter. Controls with showCount pass it for you. In the playground 0 follows what you type; any other value previews a counter state.

  • maxLengthdefault 0
    number

    Shows count / max at the end of the message row, amber from 90% and red at the limit. 0 hides the counter. The demo also sets it on the input.

  • counterLabeldefault "(remaining) => `${remaining} characters left`"
    (remaining: number) => string

    What screen readers hear once 10% or less is left, 500ms after typing stops (negative past the limit). The visible counter is hidden from them.

  • id
    string

    The control's id (not the wrapper's). Defaults to useId(); children read it from context.

  • labelBehaviordefault "native"
    "native" | "focus"

    focus: a label click focuses the control instead of clicking it, so a label never opens a picker. Button triggers set it through useFieldControl. The label carries it as data-behavior.

  • labelAsdefault "label"
    "label" | "span"

    span for group controls (radio group, checkbox group, rating, a two-thumb slider), which point at it with aria-labelledby (FieldControl adds it). Clicking it focuses the group's current stop.

  • children
    ReactNode

    The control. Inside a Field, library controls read the id, flags and messages from context; do not give them their own label (the Field's wins, with a dev warning).

  • classNames
    Partial<Record<"labelRow" | "label" | "required" | "optional" | "labelAction" | "message" | "description" | "error" | "counter", string>>

    Extra classes for the inner parts.

  • className
    string

    Classes for the root. The rest of the props (style, data-*, aria-*) land there too.

Parts

  • FieldGroup

    component

    A real <fieldset> with a <legend>, so screen readers announce the section before its fields. legend, description, error, columns (1 | 2 | 3 | 4: one column on phones, 2 from sm, 3 and 4 from md; span with sm:col-span-2), variant ("plain" | "card"), orientation and labelWidth (inherited by every field inside), gap ("sm" | "md" | "lg"), disabled (the native attribute plus context), required and requiredIndicator (a mark after the legend), classNames (legend, description, error, content). Props: FieldGroupProps.

  • FieldBox

    component

    The box every text field and trigger uses: variant ("soft" | "outline" | "underline" | "ghost"), size (inherits the Field's), radius, multiline, invalid / disabled / readOnly (default to the Field's), state ("open" | "closed" for pickers), asChild (a trigger button or a native select), focusOnPointerDown (default true: a press on the padding or an addon focuses the input inside), data-slot. Props: FieldBoxProps.

  • FieldControl

    component

    Wraps any single child (a native input or select, a range, a third-party control) and puts the Field's id, aria-describedby, aria-invalid and aria-required on it, plus disabled and readOnly while the Field has them. The child's own props win. Props: SlotProps.

  • FieldAddon

    component

    Text addons like "https://" or "USD" in the muted addon look; elements render as given and bare icons turn muted. Takes span props.

  • FieldAction

    component

    The 28px trailing icon button (32px in lg): label (required, its aria-label and title), icon, size (defaults to the Field's), keepFocus (default true, the input keeps focus while it is pressed), plus every Button prop. 44px hit area on touch. Props: FieldActionProps.

  • FieldLabel

    component

    The label on its own: as ("label" | "span"), behavior ("native" | "focus"), size, required, optional, optionalLabel, requiredIndicator, disabled. Reads its target, size and flags from the nearest Field; every prop overrides. Props: FieldLabelProps.

  • FieldDescription

    component

    Helper text in the caption style, with the Field's description id so aria-describedby finds it. Takes p props.

  • FieldError

    component

    The error text with its alert icon (icon replaces it, null hides it), with the Field's error id. Props: ComponentProps<"p"> & { icon?: ReactNode }.

  • FieldCounter

    component

    The visible "count / max", hidden from screen readers, with data-state="ok | near | over" (amber from 90%, red at the limit). Props: ComponentProps<"span"> & { count?: number; max: number }.

  • FieldMessage

    component

    The animated row under a control: description or error (never both, crossfading) plus the counter, inside a polite live region. Opens and closes with the collapse preset and works outside a Field too, e.g. under a checkbox. Props: FieldMessageProps.

  • useFieldControl

    hook

    The hook every control calls once. Merges its props with a parent Field or FieldGroup and returns id, labelId, describedBy, invalid, disabled, required, readOnly, size (the control's, else the Field's), orientation, controlProps (for a native element), ariaProps (for anything else), isWrapped and wrap(control), which renders the Field around the control when it has its own label, description, error or counter. Signature: (options: FieldControlOptions) => FieldControlState.

  • useFieldContext

    hook

    The nearest Field's ids and flags (id, labelId, describedBy, invalid, disabled, required, readOnly, size, orientation), or null outside one. For custom controls. Signature: () => FieldContextValue | null.

  • useFormReset

    hook

    Calls onReset after the element's form is reset, once the form has put its fields back, so uncontrolled controls return to their defaultValue. Signature: (ref: RefObject<HTMLElement | null>, onReset: () => void) => void.

  • fieldBoxClasses

    function

    The box classes for any element (a trigger button, a custom wrapper). Unknown values fall back to soft / md / md. Signature: (options: { variant?, size?, radius?, multiline?, className? }) => string.

  • FIELD_BOX

    constant

    The base class recipe of the field box. Import it; never retype the classes.

  • FIELD_VARIANTS

    constant

    The box classes per variant (soft, outline, underline, ghost).

  • FIELD_SIZES

    constant

    The box height and padding classes per size.

  • FIELD_MULTILINE_SIZES

    constant

    The box padding classes per size for multiline controls (textareas).

  • FIELD_RADII

    constant

    The box radius classes per size and radius.

  • FIELD_INPUT

    constant

    The classes of the bare input inside the box.

  • FIELD_ADDON

    constant

    The muted addon classes (text like "https://" or "USD").

  • FIELD_TRIGGER

    constant

    The classes of a picker trigger button styled as a field.

  • FIELD_CHEVRON

    constant

    The classes of the trailing chevron on picker triggers.

  • FIELD_HEIGHT_VAR

    constant

    The CSS variable that carries the box height, for parts that line up with it.

Examples

Validation on blur

Leave the email field and its error slides in while the form glides down; type a capital letter in the username and the hint crossfades into the error.

Horizontal settings

orientation on a card FieldGroup: labels move into a 136px column once a field is 448px wide and stack on top below that, as here in a narrow column or on a phone.

Two columns

columns={2}: one column on phones, two from 640px; the street spans both. The state is a native select in FieldBox asChild.

Box variants and sizes

soft, outline, underline and ghost at 32, 40 and 48px, each with an icon and a suffix.

Counter

12 / 70, 66 / 70 in amber from 90%, 70 / 70 in red at the limit; type to move between them. Screen readers hear the count for the last 10%.

Label action and optional

A link at the end of the label row, a FieldAction that reveals the password, and an optional mark.

Any control, and a disabled group

FieldControl wires ids and aria onto a native range; untick the checkbox and the disabled FieldGroup below comes back to life.

Hidden label

hideLabel keeps the name for screen readers in a toolbar search with a ghost box.

Dependencies

@radix-ui/react-slotmotionlucide-reactbuttonmotionui-config

Files

  • components/ui/field.tsx

FAQ

Is the React Field component free?

Yes. The source is free and needs no account: copy it from the Code tab or install it with the CLI.

How do I install Field?

Run npx wingo-ui@latest add field. It also adds the Wingo UI items it builds on (Button, Motion presets and UIProvider) and the npm packages @radix-ui/react-slot, motion and lucide-react. With shadcn, run npx shadcn@latest add https://wingo-ui.com/r/field.json.

How does Field behave on phones?

Horizontal fields put the label beside the control once the field is 448px wide and stack it on top below that, so on a phone every label sits above its field and every input is 16px so iOS never zooms.

See also

InputTextareaNumber InputCheckboxRadio GroupSwitch

Used in

Avatar UploadColor PickerSliderSwitchMasked InputDate Range PickerModel PickerCoupon inputCategory pickerCheckboxComboboxCountry Select

and 42 more.

More React form components

Form WizardInline EditInvoice line itemsRich Text EditorSchema BuilderAddress Input
All 9 React form components

Articles using this component

  • React Country Select With Flags, Search and Dial Codes

    Component guides · Nov 6, 2026 · 9 min read

  • Shadcn Number Input: Steppers, Min, Max and Decimals

    Component guides · Nov 6, 2026 · 8 min read

  • Nextjs Contact Form That Sends Email and Blocks Spam

    Build it: screens and apps · Nov 5, 2026 · 10 min read

  • Shadcn Password Input: Show/Hide, Strength Meter, Caps Lock

    Component guides · Nov 5, 2026 · 9 min read

PreviousCSV ImportNext Form Wizard

Post a listing

Product details

Step 1 of 3
USD
At least 10 characters in the title