Skip to content

Forms

newt/ui ships form controls, not a form library. Pick the one you already use and wire it to them.

form-fields is presentational on purpose: Field, Label, Input, Textarea, Select, Switch, Checkbox and FieldHelp, styled like the client’s settings panels and reading the same tokens as everything else. There is no <Form> wrapper, no resolver and no schema of its own.

That is a deliberate omission. Form state, validation and submission are a solved problem with three good answers, and a fourth wrapper around one of them is a dependency you would have to unpick the first time you needed something it did not do.

Root class newt-field
import {
  Field,
  FieldHelp,
  Input,
  Label,
  Switch,
} from "@/registry/bases/newt/ui/form-fields"

export default function FormFieldsDemo() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-6">
      <Field>
        <Label htmlFor="newt-input-example">Label</Label>
        <Input id="newt-input-example" placeholder="Placeholder" />
        <FieldHelp>Helper text</FieldHelp>
      </Field>

      <div className="flex items-center justify-between text-newt-text-primary">
        <span>Toggle label</span>
        <Switch aria-label="Toggle label" />
      </div>
    </div>
  )
}
<script setup lang="ts">
import { ref } from "vue"
import {
  Field,
  FieldHelp,
  Input,
  Label,
  Switch,
} from "~~/registry/bases/newt/ui/form-fields"

const value = ref("")
const enabled = ref(false)
</script>

<template>
  <div class="flex w-full max-w-sm flex-col gap-6">
    <Field>
      <Label for="newt-input-example">Label</Label>
      <Input
        id="newt-input-example"
        v-model="value"
        placeholder="Placeholder"
      />
      <FieldHelp>Helper text</FieldHelp>
    </Field>

    <div class="flex items-center justify-between text-newt-text-primary">
      <span>Toggle label</span>
      <Switch v-model="enabled" aria-label="Toggle label" />
    </div>
  </div>
</template>

What the components give you

A validation state that is set once. Field takes invalid; the controls inside read it from the wrapper, so a library that only knows “this field failed” says it in one place:

<Field invalid={Boolean(errors.name)}>
  <Label>Channel name</Label>
  <Input aria-invalid={Boolean(errors.name)} />
  {errors.name && <FieldHelp variant="error">{errors.name}</FieldHelp>}
</Field>

An error that is announced. FieldHelp variant="error" carries role="alert", so a message that appears after a failed submit reaches a reader who is not looking at that part of the page.

Pick a library

Library Frameworks Guide
react-hook-form React Guide
TanStack Form React + Vue Guide
vee-validate Vue Guide

All three examples validate with zod and build the same form — creating a channel — so they can be read side by side.