component

Field

A form-field wrapper that pairs any Pindoba control with a label, optional description, required/optional markers, an info tooltip, and validation error messages — and wires the accessibility between them. Field is presentational and framework-state-agnostic: you pass it a label and an error, and it derives the control’s feedback color, links label ↔ control, and exposes the right aria-* attributes. For full form state and validation, pair it with TanStack Form.

The control is rendered through a control snippet that receives a props bag to spread onto the control:

<Field label="Email" description="We'll send your receipt here." required>
  {#snippet control(props)}
    <Input {...props} bind:value type="email" />
  {/snippet}
</Field>

Basic

A label and a help description, wired to the control via for / aria-describedby.

We'll send your receipt here.

import { useState } from "react";
import { Field } from "../Field";
import { Input } from "@pindoba/react-input";

export default function DefaultDemo() {
  const [value, setValue] = useState("");

  return (
    <Field
      label="Email"
      description="We'll send your receipt here."
      control={(props) => (
        <Input
          {...props}
          value={value}
          onChange={(e) => setValue(e.target.value)}
          type="email"
          placeholder="you@example.com"
        />
      )}
    />
  );
}

Required & label tooltip

Mark a field required to render a *, and attach a labelTooltip for an inline info trigger next to the label.

import { useState } from "react";
import { Field } from "../Field";
import { Input } from "@pindoba/react-input";

export default function RequiredTooltipDemo() {
  const [value, setValue] = useState("");

  return (
    <Field
      label="API key"
      required
      labelTooltip="Find this in Settings → Developer. We never store it in plaintext."
      control={(props) => (
        <Input
          {...props}
          value={value}
          onChange={(e) => setValue(e.target.value)}
          placeholder="sk-…"
        />
      )}
    />
  );
}

Validation feedback

Pass an error (string or string array). Field renders the message in an alert region, sets aria-invalid, and flips the control’s feedback to danger automatically.

Type an @ to clear the error.

import { useState } from "react";
import { Field } from "../Field";
import { Input } from "@pindoba/react-input";

export default function ErrorDemo() {
  const [value, setValue] = useState("nope");
  // Live-derived error: empty when it looks like an email.
  const error = value.includes("@")
    ? undefined
    : "Enter a valid email address.";

  return (
    <Field
      label="Email"
      description="Type an @ to clear the error."
      error={error}
      control={(props) => (
        <Input
          {...props}
          value={value}
          onChange={(e) => setValue(e.target.value)}
          type="email"
        />
      )}
    />
  );
}

Sizes

size scales the label, description, and error text. Keep it in sync with the control’s own size. Use optionalText for an “(optional)” hint when a field isn’t required.

(optional)
(optional)
(optional)
(optional)
import { Field } from "../Field";
import { Input } from "@pindoba/react-input";
import { stack } from "@pindoba/styled-system/patterns";

const sizes = ["xs", "sm", "md", "lg"] as const;

export default function SizesDemo() {
  return (
    <div className={stack({ gap: "md", direction: "column" })}>
      {sizes.map((size) => (
        <Field
          key={size}
          label={`Size ${size}`}
          size={size}
          optionalText="(optional)"
          control={(props) => (
            <Input {...props} size={size} placeholder={size} />
          )}
        />
      ))}
    </div>
  );
}

Horizontal layout

Set orientation="horizontal" to place the label beside the control.

Shown publicly.

---
import Field from "../field.astro";
import Input from "@pindoba/astro-input";
---

<Field
  label="Display name"
  orientation="horizontal"
  description="Shown publicly."
>
  <Input placeholder="Ada Lovelace" />
</Field>

Forms with TanStack Form

Field is the presentational half of Pindoba’s forms story. To wire it to real form state and validation, use the TanStack Form block — it re-exports createForm and ships ready-made <FormInput>, <FormSelect>, <FormCheckbox>, a generic <FormField>, and a <FormSubmit> button, with Standard Schema validation (Zod / Valibot / ArkType).

<script lang="ts">
  import { z } from "zod";
  import {
    createForm,
    FormInput,
    FormSubmit,
    focusInvalidOnSubmit,
  } from "../index.js";
  import { stack } from "@pindoba/styled-system/patterns";

  const schema = z.object({
    name: z.string().min(2, "Use at least 2 characters."),
    email: z.string().email("Enter a valid email address."),
  });

  let submitted = $state<string | null>(null);

  const form = createForm(() => ({
    defaultValues: { name: "", email: "" },
    validators: { onChange: schema },
    onSubmit: async ({ value }) => {
      submitted = JSON.stringify(value);
    },
  }));
</script>

<form
  onsubmit={(event) => {
    event.preventDefault();
    form.handleSubmit();
  }}
  {@attach focusInvalidOnSubmit(form)}
>
  <div class={stack({ gap: "md", direction: "column" })}>
    <FormInput {form} name="name" label="Name" required />
    <FormInput
      {form}
      name="email"
      label="Email"
      type="email"
      required
      labelTooltip="We email your receipt here."
    />
    <FormSubmit {form}>Create account</FormSubmit>
    {#if submitted}<p>Submitted: {submitted}</p>{/if}
  </div>
</form>
props · 16 shown · 16 total
control required slot svelte
Snippet<[FieldControlProps]>

Render-prop for the control — receives the props bag to spread onto it.

description svelte
string

Help text rendered under the control and wired via aria-describedby.

disabled
boolean
default false

Disables the field: dims it and forwards `disabled` to the control.

error
nullstringstring[]

Validation error(s). A non-empty value flips the control's `feedback` to `danger`, renders the message(s) in an `alert` region, and sets `aria-invalid` / `aria-errormessage` on the control.

feedback
"primary""neutral""success""warning""danger"

Explicit feedback override for the control. When unset, the field derives `danger` from the presence of an `error`, and otherwise leaves the control's own resting default untouched (e.g. Input `neutral`, Checkbox `primary`).

hasLabel
boolean

Whether a visible label is rendered. When true (and an `id` is set), the connect seeds a label id and associates the control via `aria-labelledby`. Frameworks set this when label content is provided.

hasTooltip
boolean

Whether a label tooltip trigger is rendered, so the connect emits its styling bag. Frameworks set this when a tooltip is provided.

id
string

Stable id linking the `<label for>` to the control and seeding the `aria-describedby` / `aria-errormessage` ids. Frameworks generate one when the consumer doesn't pass it. When absent, the label/describedby wiring is omitted (the field still renders).

label svelte
string

Label text shown above (or beside) the control.

labelTooltip slot svelte
stringSnippet

Tooltip attached to a small info trigger next to the label.

optionalText
string

Optional-hint text shown after the label when the field is NOT required (e.g. "(optional)"). Ignored when `required` is true.

orientation
"vertical""horizontal"
default "vertical"

Label/control layout direction.

passThrough
FieldPassThrough

Per-slot escape hatch for custom styling and props.

required
boolean
default false

Marks the field as required, rendering a `*` after the label.

size
"sm""md""lg""xs"
default "md"

Size variant, kept in sync with the control's size scale.

tooltipPlacement svelte
Placement

Placement of the label tooltip.

Type

  • Components
  • Blocks