Field
Standalone field layout that sits beside Form.
Preview
Loading...
import {
Field,
FieldDescription,
FieldError,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
} from "@/components/ui/Field";
import { Input } from "@/components/ui/Input";
export function FieldDemo() {
return (
<FieldSet className="w-full">
<FieldLegend>Profile</FieldLegend>
<FieldGroup>
<Field>
<FieldLabel htmlFor="field-name">Name</FieldLabel>
<Input id="field-name" placeholder="Ada Lovelace" />
<FieldDescription>Shown on the care plan.</FieldDescription>
</Field>
<Field data-invalid="true">
<FieldLabel htmlFor="field-email">Email</FieldLabel>
<Input id="field-email" defaultValue="not-an-email" aria-invalid />
<FieldError>Enter a valid email.</FieldError>
</Field>
</FieldGroup>
</FieldSet>
);
}Installation
Make sure that namespace is set in your component.json file. Namespace docs: Learn more about namespaces
pnpm dlx shadcn@latest add @aura/fieldManual
Install the following dependencies:
pnpm install class-variance-authorityCopy and paste the class names utility into your utils/class-names.ts file.
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Copy and paste the separator component into your components/ui/Separator.tsx file.
"use client"
import * as React from "react"
import {Separator as SeparatorPrimitive} from "radix-ui"
import { cn } from "@/utils/class-names"
function Separator({
className,
orientation = "horizontal",
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
<SeparatorPrimitive.Root
data-slot="separator"
decorative={decorative}
orientation={orientation}
className={cn(
"bg-gray-a6 shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className
)}
{...props}
/>
)
}
export { Separator }Copy and paste the Field component into your components/ui/Field.tsx file.
"use client";
/**
* @description Standalone field layout. Use beside Form, not instead of FormField.
*/
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { Separator } from "@/components/ui/Separator";
import { cn } from "@/utils/class-names";
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
return (
<fieldset
data-slot="field-set"
className={cn("flex flex-col gap-1 border-0 p-0", 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-0.5 font-medium text-gray-12",
variant === "label" && "text-sm",
className,
)}
{...props}
/>
);
}
function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-group"
className={cn("flex w-full flex-col gap-1", className)}
{...props}
/>
);
}
const fieldVariants = cva("flex w-full gap-0.5", {
variants: {
orientation: {
vertical: "flex-col",
horizontal: "flex-row items-center",
},
},
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("flex flex-1 flex-col gap-0.5", className)}
{...props}
/>
);
}
function FieldLabel({ className, ...props }: React.ComponentProps<"label">) {
return (
<label
data-slot="field-label"
className={cn("text-gray-12", className)}
{...props}
/>
);
}
function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="field-title"
className={cn("font-medium text-gray-12", className)}
{...props}
/>
);
}
function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<p
data-slot="field-description"
className={cn("text-sm text-gray-11", className)}
{...props}
/>
);
}
function FieldSeparator({
children,
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="field-separator"
className={cn("relative flex items-center", className)}
{...props}
>
<Separator className="absolute inset-x-0" />
{children ? (
<span className="relative bg-gray-1 px-1 text-sm text-gray-11">
{children}
</span>
) : null}
</div>
);
}
function FieldError({
className,
children,
errors,
...props
}: React.ComponentProps<"div"> & {
errors?: Array<{ message?: string } | undefined>;
}) {
const content = React.useMemo(() => {
if (children) return children;
const messages = errors?.map((error) => error?.message).filter(Boolean);
if (!messages?.length) return null;
if (messages.length === 1) return messages[0];
return (
<ul className="flex list-disc flex-col gap-0.5 ps-2">
{messages.map((message) => (
<li key={message}>{message}</li>
))}
</ul>
);
}, [children, errors]);
if (!content) return null;
return (
<div
role="alert"
data-slot="field-error"
className={cn("text-sm text-danger-contrast", className)}
{...props}
>
{content}
</div>
);
}
export {
Field,
FieldLabel,
FieldDescription,
FieldError,
FieldGroup,
FieldLegend,
FieldSeparator,
FieldSet,
FieldContent,
FieldTitle,
};