User
Pick a person in an Auto Form field that stores the user id, with each name in its role color.
Preview
Usage
import { z } from 'zod'
import { AutoForm } from '@vitnode/core/components/form/auto-form'
import { AutoFormUser } from '@vitnode/core/components/form/fields/input-users'
import { searchAdminUsersInBrowser } from '@vitnode/core/tanstack/admin/users'const formSchema = z.object({ authorId: z.number() })
<AutoForm
formSchema={formSchema}
fields={[
{
id: 'authorId',
component: props => (
<AutoFormUser
{...props}
label="Author"
placeholder="Select an author"
search={searchAdminUsersInBrowser}
/>
),
},
]}
/>The value is the user id, a plain number. search is required. In the
AdminCP, searchAdminUsersInBrowser reads the AdminCP users list, so it needs
an AdminCP session and returns only what that route allows.
Editing an existing record
A picker can't show a name it never fetched. Pass the saved person as
selected, or the field opens on the placeholder. clearable adds a button
that sets the value to null, so pair it with z.number().nullable().
<AutoFormUser
{...props}
clearable
label="Reviewer"
search={search}
selected={{ id: post.reviewer.id, name: post.reviewer.name }}
/>selected can be partial: just id and name. Without avatarColor, the
field draws a neutral placeholder avatar instead of guessing a color.
Role colors
Add a role to each option and the picker draws the name the way the rest of
the site does: in the role's color, with its prefix icon or emoji in front.
Options without a role keep a plain name, so you can add it gradually.
const moderators: UserOption[] = [
{
avatarColor: '2563eb',
id: 1,
name: 'Ada Lovelace',
nameCode: 'ada',
role: { color: '#dc2626', prefix: 'icon:shield' },
},
]prefix takes the same icon:<lucide-name> or emoji:<emoji> value as a
role's prefix in the AdminCP. In the AdminCP, searchAdminUsersInBrowser
already returns each person's primary role, so its pickers show colored names
without extra work.
Custom search
search can call any endpoint: a plugin's own members, fixtures in a test.
It runs with "" each time the picker opens, then debounced as the person
types. Results are never cached, so deleted users don't linger.
<AutoFormUser
{...props}
label="Reviewer"
search={(value) => searchReviewers(value)}
/>Props
| Prop | Type | Default | What it does |
|---|---|---|---|
search | (value: string) => Promise<UserOption[]> | required | Looks up people |
label | ReactNode | - | Field label |
description | ReactNode | - | Help text under the control |
placeholder | string | Select an option | Shown while nobody is chosen |
searchPlaceholder | string | Search... | Placeholder inside the search box |
selected | PartialUserOption | null | - | The person the field opens on |
clearable | boolean | false | Adds a button that sets null |
disabled | boolean | false | Blocks opening the picker and clearing |
UserOption is { id, name, nameCode, avatarColor, role? }, where role is
{ color: string | null, prefix?: string | null }. PartialUserOption makes
nameCode and avatarColor optional.
In the Content Engine
A field.user() renders this field, like
the blog post author picker. Its options come from the content type's own
picker route, so editors can pick an author without browsing the member list.
See also
- User Format - show a member's name outside a form.
- Roles - the same idea for roles, single or multiple.
- Combobox - when the options are strings, not people.