Text field
One line of text, with optional addons beside it.
Installation
npx shadcn@latest add @formdef/text-fieldRegister it under widgets in lib/formdef/index.tsx, as widgets: { text: TextField }. The
widget serves string values, so the schema's input at its path is a string.
Usage
export const = createForm({
: .({ : .().(1, 'Enter your name'), : .() }),
: { : '', : '' },
fields: {
name: { : 'text', : { : 'Name', : 'name' } },
email: { : 'text', : { : 'Email', : 'email', : 'email' } },
},
})type takes the text-like input types: text, email, search, tel and url. A password has
a widget of its own, and so does a whole number.
Addons
addons places content inside the input's border, such as a currency or a unit. Each addon needs an
id, which React uses as its key, and takes an align:
export const = createForm({
: .({ : .() }),
: { : '' },
fields: {
domain: {
: 'text',
: {
: 'Domain',
: [
{ : 'scheme', : 'https://', : 'inline-start' },
{ : 'suffix', : '.com', : 'inline-end' },
],
},
},
},
})Amounts
An amount or a decimal is a text field with inputMode: 'decimal'. A phone shows a keypad with a
decimal separator, and the comma a German user types stays a comma. An <input type="number">
would turn it into a dot or drop it.
The value stays a string. TanStack Form hands onSubmit the values as they were typed, not the
schema's output, so convert the amount where you read it:
const = /^\d+(,\d{1,2})?$/
const = 100
const = (: string): number =>
.((.(',', '.')) * )
export const = createForm({
: .({ : .().(, 'Enter an amount such as 12,50') }),
: { : '' },
fields: {
amount: { : 'text', : { : 'Amount', : 'decimal' } },
},
onSubmit: ({ }) => ((.)),
})Props
Prop
Type
Source
import { type FieldOf, fieldParts } from '@formdef/core'
import type { ComponentProps } from 'react'
import { FieldFrame } from '@/components/formdef/field-frame'
import { Input } from '@/components/ui/input'
import { InputGroup, InputGroupAddon, InputGroupInput } from '@/components/ui/input-group'
import type { Addon } from '@/lib/formdef/addons'
import type { FieldLabels } from '@/lib/formdef/field-labels'
type Attrs = Pick<
ComponentProps<'input'>,
| 'className'
| 'placeholder'
| 'autoComplete'
| 'autoFocus'
| 'disabled'
| 'readOnly'
| 'required'
| 'maxLength'
| 'inputMode'
| 'spellCheck'
> & {
type?: 'text' | 'email' | 'search' | 'tel' | 'url'
}
type TextFieldProps = FieldOf<string> &
FieldLabels &
Attrs & {
addons?: Addon[]
}
export const TextField = ({
field,
label,
labelAside,
description,
addons = [],
...attrs
}: TextFieldProps) => {
const parts = fieldParts(field, { description })
const value = field.state.value ?? ''
const hasAddons = addons.length > 0
const Control = hasAddons ? InputGroupInput : Input
const input = (
<Control
type="text"
{...attrs}
{...parts.control}
value={value}
onChange={(event) => field.handleChange(event.target.value)}
/>
)
return (
<FieldFrame
parts={parts}
label={label}
labelAside={labelAside}
description={description}
disabled={attrs.disabled}
>
{hasAddons ? (
<InputGroup>
{input}
{addons.map((addon) => (
<InputGroupAddon key={addon.id} align={addon.align}>
{addon.content}
</InputGroupAddon>
))}
</InputGroup>
) : (
input
)}
</FieldFrame>
)
}