--- title: Compose a profile form description: Combine Field, Input, Select, and Button into an accessible form while keeping state in the application. order: 30 toc: - id: complete-example title: Complete example - id: validation title: Validation state - id: controlled-select title: Controlled Select - id: styling title: Style overrides --- ## Complete example {#complete-example} ```tsx import * as React from "react" import { Button } from "@workspace/ui/components/button" import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel, } from "@workspace/ui/components/field" import { Input } from "@workspace/ui/components/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@workspace/ui/components/select" export function ProfileForm() { const [role, setRole] = React.useState("editor") const [error, setError] = React.useState("") function submit(event: React.FormEvent) { event.preventDefault() const data = new FormData(event.currentTarget) setError(data.get("displayName") ? "" : "Enter a display name") } return (
Display name This name is visible to other members. {error} Role
) } ``` ## Validation state {#validation} The UI package does not select a validation library. The application produces errors and connects the same state to `Field data-invalid`, the control's `aria-invalid`, and `FieldError`. Multiple schema errors can be passed through `FieldError errors={errors}`; the component deduplicates them before rendering a list. ## Controlled Select {#controlled-select} The local Select is built on Base UI and uses `value` with `onValueChange`. `SelectTrigger` renders its own arrow, so it only needs `SelectValue`; do not add another chevron. The popup aligns to the trigger and adopts its width by default. Adjust `side`, `align`, `sideOffset`, and `alignItemWithTrigger` on `SelectContent` when required. ## Style overrides {#styling} Use the existing `variant`, `size`, and structural parts first, then apply page layout through `className`. Components merge classes with `cn`, allowing consumer Tailwind classes to override default utilities. Promote overrides repeated across pages into a new variant or composite component instead of copying long class strings.