Documentation

User

Pick a person in an Auto Form field that stores the user id, with each name in its role color.

Preview

Search by name, pick one person.

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

Opens on the saved reviewer. Clear it to unassign.

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

Names use each person's role color and prefix.

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.

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

PropTypeDefaultWhat it does
search(value: string) => Promise<UserOption[]>requiredLooks up people
labelReactNode-Field label
descriptionReactNode-Help text under the control
placeholderstringSelect an optionShown while nobody is chosen
searchPlaceholderstringSearch...Placeholder inside the search box
selectedPartialUserOption | null-The person the field opens on
clearablebooleanfalseAdds a button that sets null
disabledbooleanfalseBlocks 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.