Summary
Read-only table column for displaying users with the same visual language as UserSelect. Automatically picks the layout based on how many users are in the cell state:| Users in state | Display |
|---|---|
| 0 | Empty cell |
| 1 | Rich row: avatar + name + email + verified badge |
| 2+ | Overlapping circular avatar stack with +N overflow badge |
| Class | Bjanczak\FilamentFlexFields\Filament\Tables\Columns\UserColumn |
| Context | Filament tables ($table->columns([...])) |
| State type | Model, Collection of models, or list<Model> |
| Parent | Filament\Tables\Columns\TextColumn |
ResolvesUserDisplay concern (nameColumn, emailColumn, avatarColumn, custom resolvers).
Basic usage
Single user (BelongsTo / HasOne):$record->author or $record->members as column state. UserColumn automatically calls with() for direct relationship column names (for example members, author) and any extra relations declared via eagerLoad().
Custom getStateUsing() that returns the same users on every row should use sharedStackUsing() (preferred) or resolve models once per request on the page/resource.
Custom avatar via Filament convention:
Avatar resolution order
getAvatarUrlUsing()callback, if setgetFilamentAvatarUrl()on the model, when the method existsavatarColumn()value on the model- Initials on a gradient surface when no URL is available
Configuration API (UserColumn-specific)
maxVisibleAvatars(int|Closure $limit)
Maximum avatars shown in stack mode before the +N overflow badge. Default: 4, minimum 1.
stackedRing(int|Closure $ring)
White ring width (px) around each stacked avatar. Default: 2.
stackedOverlap(int|Closure $overlap)
Horizontal overlap (px) between stacked avatars. Default: 10.
stackTooltips(bool|Closure $condition = true)
Show each user’s name in a native title tooltip on stack avatars. Default: true.
eagerLoad(string|array|Closure $relationships)
Register extra relationships to eager-load for this column. Direct relationship column names such as members or author are eager-loaded automatically when they match an Eloquent relation on the table model.
sharedStackUsing(Closure $resolver)
Resolve the same multi-user stack once per table page instead of querying or building state on every row. Use for preview/demo columns that show identical members on each record.
Performance
| Mechanism | What it does |
|---|---|
applyEagerLoading() | During table query build, adds with() for the column name when it matches an Eloquent relation (members, author, …) plus any eagerLoad() relations. |
UserColumnStackState | Wraps multi-user state so Filament TextColumn treats it as one HTML cell (avoids comma-joined rich rows). |
UserColumnRenderCache | Per-request cache of rendered rich/stack HTML keyed by normalized display data and column options. Identical stacks across rows reuse one Blade render. |
sharedStackUsing() | Per-page cache of shared multi-user state — resolver runs once per Livewire table, not once per row. |
| Lazy CSS | Loads flex-fields-user-display.css + flex-fields-user-column.css only when a UserColumn cell renders (via load-stylesheet partial). Request-scoped queue + emit-assets deduplication prevent duplicate <link> tags; data-navigate-track + flex-field-asset-injector keep SPA navigation clean. |
Shared user display API
These methods are identical to UserSelect:| Method | Description |
|---|---|
nameColumn(string|Closure $column) | Name attribute. Default: name |
emailColumn(string|Closure|null $column) | Email / subtitle column |
avatarColumn(string|Closure|null $column) | Avatar URL column |
verificationColumn(string|Closure|null $column) | Verified badge column |
getAvatarUrlUsing(?Closure $callback) | Custom avatar resolver |
getNameUsing(?Closure $callback) | Custom name resolver |
getEmailUsing(?Closure $callback) | Custom email resolver |
isVerifiedUsing(?Closure $callback) | Custom verified resolver |
Inherited TextColumn API
All FilamentTextColumn methods apply: label(), sortable(), searchable(), toggleable(), alignStart() / alignCenter(), url(), tooltip(), etc. The column uses html() internally — do not call html(false) unless you override formatStateUsing().
Public helper methods
| Method | Returns | Description |
|---|---|---|
formatUserDisplay(mixed $state) | string | Rendered HTML for a state value |
normalizeUsersFromState(mixed $state) | list<array> | Normalized user display arrays |
recordToDisplayArray(Model $record) | array | label, description, image, verified, initials |
renderRichUser(array $user) | string | Single-user rich HTML |
renderAvatarStack(array $users) | string | Multi-user stack HTML |
getMaxVisibleAvatars() | int | Stack visibility limit |
getStackedRing() | int | Stack ring width (px) |
getStackedOverlap() | int | Stack overlap (px) |
shouldShowStackTooltips() | bool | Whether stack items show name tooltips |
resolveUserDisplayInitials(string $name) | string | Initials fallback |
CSS classes
| Class | Role |
|---|---|
fff-user-column | Cell wrapper |
fff-user-column--rich | Single-user layout |
fff-user-column--stacked | Multi-user stack layout |
fff-user-column__avatar-stack | Overlapping avatar row |
fff-user-column__avatar-stack-item | Individual stack avatar |
fff-user-column__avatar-stack-overflow | +N overflow badge |
fff-user-select__avatar--stack | Stack avatar size modifier |
Implementation notes
- Lazy-loads
user-display+user-columnCSS bundles when the column renders (not part offlex-fields-core.css). - Multi-user state is wrapped internally so Filament does not comma-join multiple rich user rows.
- Stack mode hides the verified badge on individual avatars (names are available via tooltip when
stackTooltips()is enabled). - Only Eloquent
Modelinstances in state are rendered; scalar IDs are not resolved automatically — use a relationship column with auto eager-load, or a customgetStateUsing()that returns models (cached when shared across rows).