User
Pick a person in an Auto Form field that stores the user id, or show a member's name with their role color and a link to their profile.
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.
Show a member's name
UserFormat prints a name the way the rest of the site does. format adds the
role color and prefix, and link turns the name into a link to the member's
profile at /users/<nameCode>.
import { UserFormat } from '@vitnode/core/components/user-format'
<UserFormat format link user={user} />user is { name, nameCode?, role: { color, prefix? } }. link needs a
nameCode; without one, the name renders as plain text. Leave link off when
the name sits inside another link or a button, such as a table row that
already opens something.
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.