Logo VitNode
Documentation

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.

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

  • Roles - the same idea for roles, single or multiple.
  • Combobox - when the options are strings, not people.