PRIZM 4.0DSTA
All components

Field

Form field wrapper with label, hint, and error.

stableBase UI Field

Preview

Used as the prefix for issue IDs. Cannot be changed later.

Code

tsx
import { Field, FieldLabel, FieldDescription } from "@/components/ui/field";
import { Input } from "@/components/ui/input";

export function Example() {
  return (
    <Field>
      <FieldLabel>Project key</FieldLabel>
      <Input placeholder="PRIZM" />
      <FieldDescription>
        Used as the prefix for issue IDs. Cannot be changed later.
      </FieldDescription>
    </Field>
  );
}

Props

PropTypeDefaultDescription
namestringField name, used for form submission.
validate(value: unknown) => string | nullSync validator returning an error message or null.
validationMode"onBlur" | "onChange""onBlur"When validation runs.
classNamestringAdditional Tailwind classes, merged with the component's defaults via cn().

Sub-components

FieldLabelLabel associated automatically via Field context.
FieldControlSlot for the actual control (Input, Textarea).
FieldDescriptionHelper / hint text.
FieldErrorValidation error message — auto-shown when invalid.
FieldValidityRender-prop access to validity state for custom error UIs.

Built on Base UI Field. Wires `aria-describedby` automatically between label, control, hint, and error.

All components also accept standard HTML attributes for their root element (e.g. `id`, `aria-*`, `data-*`, event handlers) and forward `ref` where applicable.

Source

The full implementation of components/ui/field.tsx is below — copy it into your project and own it. Some components import the cn helper from lib/utils or other primitives; copy those too.

tsx
"use client";

import { cn } from "@/lib/utils";
import { Field as BaseField } from "@base-ui-components/react/field";
import type { ComponentPropsWithoutRef } from "react";

export function Field({ className, ...props }: ComponentPropsWithoutRef<typeof BaseField.Root>) {
  // Bake the label/control/hint/error rhythm in (matches PrizmField's setSpacing(6)),
  // so a field has vertical spacing without the consumer adding space-y-*.
  return <BaseField.Root className={cn("grid gap-1.5", className)} {...props} />;
}

export function FieldLabel({
  className,
  ...props
}: ComponentPropsWithoutRef<typeof BaseField.Label>) {
  return (
    <BaseField.Label
      className={cn("text-sm font-medium leading-tight text-fg", className)}
      {...props}
    />
  );
}

export function FieldDescription({
  className,
  ...props
}: ComponentPropsWithoutRef<typeof BaseField.Description>) {
  return (
    <BaseField.Description
      className={cn("text-xs leading-relaxed text-fg-muted", className)}
      {...props}
    />
  );
}

export function FieldError({
  className,
  ...props
}: ComponentPropsWithoutRef<typeof BaseField.Error>) {
  return (
    <BaseField.Error className={cn("text-xs leading-relaxed text-danger", className)} {...props} />
  );
}

export const FieldControl = BaseField.Control;
export const FieldValidity = BaseField.Validity;

JavaFX

Ships in the PRIZM JavaFX library for thick-client C3 apps as PrizmField (extends VBox). Run the gallery to see it natively.

java
import design.prizm.fx.controls.PrizmField;

PrizmField()
PrizmField(String labelText)
PrizmField(String labelText, Node control)
PropTypeDefaultDescription
setLabel(String) → voidSet the field label text.
setControl(Node) → voidSet/replace the wrapped control; pairs it with the label (labelFor).
setDescription(String) → voidHelper / hint text below the control; null or "" hides it.
setError(String) → voidValidation error below the field; null or "" clears it.
labelNode() → PrizmLabelThe label, for further styling.

A composite over VBox (no stock 1:1 control), styled by the .prizm-field rules in prizm.css. Carries no validation engine (a web concern) — drive the error line with setError. Mirrors Field + FieldLabel + FieldControl + FieldDescription + FieldError in components/ui/field.tsx.