component
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>
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"
/>
)}
/>
);
}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-…"
/>
)}
/>
);
}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"
/>
)}
/>
);
}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.
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>
);
}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>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>Render-prop for the control — receives the props bag to spread onto it.
Help text rendered under the control and wired via aria-describedby.
falseDisables the field: dims it and forwards `disabled` to the control.
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.
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`).
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.
Whether a label tooltip trigger is rendered, so the connect emits its styling bag. Frameworks set this when a tooltip is provided.
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 text shown above (or beside) the control.
Tooltip attached to a small info trigger next to the label.
Optional-hint text shown after the label when the field is NOT required (e.g. "(optional)"). Ignored when `required` is true.
"vertical"Label/control layout direction.
Per-slot escape hatch for custom styling and props.
falseMarks the field as required, rendering a `*` after the label.
"md"Size variant, kept in sync with the control's size scale.
Placement of the label tooltip.