Sabil UI

Search documentation

Search for a command to run...

Components

Field

Combine labels, controls, and help text to compose accessible form fields.

Payment Method

All transactions are secure and encrypted

Enter your 16-digit card number

Billing Address

The billing address associated with your payment method

Installation#

  1. Install the dependencies

    npm install class-variance-authority cn
  2. Copy the source into your project

    components/ui/field.tsx
    "use client"import { useMemo } from "react"import { cva, type VariantProps } from "class-variance-authority"import { cn } from "cn"import { Label } from "@/components/ui/label"import { Separator } from "@/components/ui/separator"function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {  return (    <fieldset      data-slot="field-set"      className={cn(        "flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",        className      )}      {...props}    />  )}function FieldLegend({  className,  variant = "legend",  ...props}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {  return (    <legend      data-slot="field-legend"      data-variant={variant}      className={cn(        "mb-1.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",        className      )}      {...props}    />  )}function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {  return (    <div      data-slot="field-group"      className={cn(        "group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",        className      )}      {...props}    />  )}const fieldVariants = cva(  "group/field flex w-full gap-2 data-[invalid=true]:text-destructive",  {    variants: {      orientation: {        vertical: "flex-col *:w-full [&>.sr-only]:w-auto",        horizontal:          "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",        responsive:          "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",      },    },    defaultVariants: {      orientation: "vertical",    },  })function Field({  className,  orientation = "vertical",  ...props}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {  return (    <div      role="group"      data-slot="field"      data-orientation={orientation}      className={cn(fieldVariants({ orientation }), className)}      {...props}    />  )}function FieldContent({ className, ...props }: React.ComponentProps<"div">) {  return (    <div      data-slot="field-content"      className={cn(        "group/field-content flex flex-1 flex-col gap-0.5 leading-snug",        className      )}      {...props}    />  )}function FieldLabel({  className,  ...props}: React.ComponentProps<typeof Label>) {  return (    <Label      data-slot="field-label"      className={cn(        "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border has-[>[data-slot=field]]:not-has-[:disabled,[data-disabled]]:hover:bg-muted/50 has-[>[data-slot=field]]:has-[:focus-visible]:border-ring has-[>[data-slot=field]]:has-[:focus-visible]:ring-3 has-[>[data-slot=field]]:has-[:focus-visible]:ring-ring/50 *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",        "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",        className      )}      {...props}    />  )}function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {  return (    <div      data-slot="field-label"      className={cn(        "flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50",        className      )}      {...props}    />  )}function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {  return (    <p      data-slot="field-description"      className={cn(        "text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",        "last:mt-0 nth-last-2:-mt-1",        "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",        className      )}      {...props}    />  )}function FieldSeparator({  children,  className,  ...props}: React.ComponentProps<"div"> & {  children?: React.ReactNode}) {  return (    <div      data-slot="field-separator"      data-content={!!children}      className={cn(        "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",        className      )}      {...props}    >      <Separator className="absolute inset-0 top-1/2" />      {children && (        <span          className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"          data-slot="field-separator-content"        >          {children}        </span>      )}    </div>  )}function FieldError({  className,  children,  errors,  ...props}: React.ComponentProps<"div"> & {  errors?: Array<{ message?: string } | undefined>}) {  const content = useMemo(() => {    if (children) {      return children    }    if (!errors?.length) {      return null    }    const uniqueErrors = [      ...new Map(errors.map((error) => [error?.message, error])).values(),    ]    if (uniqueErrors?.length == 1) {      return uniqueErrors[0]?.message    }    return (      <ul className="ml-4 flex list-disc flex-col gap-1">        {uniqueErrors.map(          (error, index) =>            error?.message && <li key={index}>{error.message}</li>        )}      </ul>    )  }, [children, errors])  if (!content) {    return null  }  return (    <div      role="alert"      data-slot="field-error"      className={cn("text-sm font-normal text-destructive", className)}      {...props}    >      {content}    </div>  )}export {  Field,  FieldLabel,  FieldDescription,  FieldError,  FieldGroup,  FieldLegend,  FieldSeparator,  FieldSet,  FieldContent,  FieldTitle,}
  3. Make sure the foundation is in place

    Components read their colors, radius and surfaces from the tokens in app/globals.css. See Theming.

Usage#

import {  Field,  FieldLabel,  FieldDescription,  FieldError,  FieldGroup,  FieldLegend,  FieldSeparator,  FieldSet,  FieldContent,  FieldTitle,} from "@/components/ui/field"

Examples#

Checkbox

Show these items on the desktop

Select the items you want to show on the desktop.

Your Desktop & Documents folders are being synced with iCloud Drive. You can access them from other devices.

Choice Card

Compute Environment

Select the compute environment for your cluster.

Fieldset

Address Information

We need your address to deliver your order.

Group

Get notified when ChatGPT responds to requests that take time, like research or image generation.

Get notified when tasks you've created have updates. Manage tasks

Input

Choose a unique username for your account.

Must be at least 8 characters long.

Radio

Subscription Plan

Yearly and lifetime plans offer significant savings.

Responsive

Profile

Fill in your profile information.

Provide your full name for identification

Select

Select your department or area of work.

Slider

Price Range

Set your budget range ($200 - 800).

Switch

Textarea

Share your thoughts about our service.