Field
Composable form-field parts: label, description, error, group, and fieldset with horizontal/vertical layout.
Installation
npx cano-ui add field
Installs label, class-variance-authority automatically if missing.
Source
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
return (
<fieldset
data-slot="field-set"
className={cn("flex flex-col gap-6", className)}
{...props}
/>
)
}
function FieldLegend({
className,
...props
}: React.ComponentProps<"legend">) {
return (
<legend
data-slot="field-legend"
className={cn("mb-3 font-medium", className)}
{...props}
/>
)
}
const fieldGroupVariants = cva("flex w-full gap-6 data-[slot=checkbox-group]:gap-3", {
variants: {},
defaultVariants: {},
})
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-group"
className={cn(fieldGroupVariants(), "flex-col", className)}
{...props}
/>
)
}
const fieldVariants = cva("group/field flex w-full gap-2 data-[invalid=true]:text-destructive", {
variants: {
orientation: {
vertical: "flex-col [&>*]:w-full",
horizontal: "flex-row items-center [&>[data-slot=field-content]]:flex-1",
},
},
defaultVariants: {
orientation: "vertical",
},
})
function Field({
className,
orientation = "vertical",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
return (
<div
data-slot="field"
role="group"
data-orientation={orientation}
className={cn(fieldVariants({ orientation }), className)}
{...props}
/>
)
}
function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-content"
className={cn("flex flex-1 flex-col gap-1.5 leading-snug", className)}
{...props}
/>
)
}
function FieldLabel({
className,
...props
}: React.ComponentProps<typeof Label>) {
return (
<Label
data-slot="field-label"
className={cn(
"group-data-[disabled=true]/field:opacity-50",
className
)}
{...props}
/>
)
}
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-title"
className={cn("flex w-fit items-center gap-2 text-sm font-medium leading-snug", className)}
{...props}
/>
)
}
function FieldDescription({
className,
...props
}: React.ComponentProps<"p">) {
return (
<p
data-slot="field-description"
className={cn(
"text-sm font-normal leading-normal text-muted-foreground",
className
)}
{...props}
/>
)
}
function FieldError({
className,
children,
...props
}: React.ComponentProps<"p">) {
if (!children) return null
return (
<p
data-slot="field-error"
role="alert"
className={cn("text-sm font-normal text-destructive", className)}
{...props}
>
{children}
</p>
)
}
function FieldSeparator({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-separator"
className={cn("my-1 h-px bg-border", className)}
{...props}
/>
)
}
export {
Field,
FieldSet,
FieldLegend,
FieldGroup,
FieldContent,
FieldLabel,
FieldTitle,
FieldDescription,
FieldError,
FieldSeparator,
}
This exact file lands in your project at components/ui/field.tsx. It is yours to edit.