Entries & Assets fields¶
Relation fields (Entries, Assets, Categories, Tags, Users…) all render through the same elementSelect UI: a stack of selected elements plus an Add button that opens a selector modal. What that stack looks like is driven by the field’s settings — chiefly viewMode, maxRelations, and selectionLabel — so the same field type produces quite different UIs.
Entries field — list view¶
The default. Related entries stack as draggable chips with a status dot and label. Here maxRelations: 3 and a custom selectionLabel.
Trail Runner GTX Alpine Down Jacket
<ul class="elements chips chips-small"> <!-- (1)! -->
<li>
<div id="chip-1897433908" class="chip small element removable" <!-- (2)! -->
data-type="craft\elements\Category" data-id="1063" data-label="Special Events"
data-status="enabled" data-cp-url="…" data-editable data-deletable>
<div class="chip-content">
<span class="status enabled" role="img" aria-label="Status: Enabled"></span>
<craft-element-label id="chip-1897433908-label" class="label"> <!-- (3)! -->
<span class="label-link">Special Events</span>
</craft-element-label>
<div class="chip-actions"> <!-- (4)! -->
<button type="button" class="btn action-btn small" data-icon="ellipsis"
aria-label="Actions" data-disclosure-trigger="true" aria-expanded="false"></button>
<button type="button" class="btn chromeless small move-btn" title="Reorder"
aria-label="Reorder" aria-describedby="chip-1897433908-label">
<div class="inline-flex gap-xs"><div class="cp-icon"><!-- svg --></div></div>
</button>
</div>
<input type="hidden" name="fields[newsCategory][]" value="1063"> <!-- (5)! -->
</div>
</div>
</li>
</ul>
- One chip per
<li>. The<ul>carries three classes —elements chips chips-small— and.chipsis a flex column. - Four classes, from two different places.
chipandsmallcome fromCp::chipHtml();elementandremovablecome frombaseElementAttributes().removableappears only whencontext: 'field'— it's what puts the × on hover. An invalid element picks uperrorthe same way. - Not
.chip-label.Cp::chipHtml()produces that generically, butelementChipHtml()overrides it with this<craft-element-label>custom element wrapping a.label-linkspan. Style.chip .labelor you'll miss every element chip. - Pushed to the trailing edge with
margin-inline-start: auto— there's no spacer element. The⋯is a disclosure menu trigger. - Inside
.chip-content, not a sibling. Remove the chip and the value goes with it — that's how the field posts its selection.
The chip's hairline is a ::before overlay rather than a border, which is how it sits above the thumb without the corners poking through.
Note
align-items: flex-start is what makes chips hug. .chips is a flex column, so without it every <li> stretches and each chip runs the full field width. Craft sets .chips{align-items:flex-start; gap:var(--xs)} — if your chips look like full-width bars instead of pills, that's the missing declaration.
The Add button is a dashed ghost button — btn add icon dashed wrap — not the solid grey .btn default. Once elements|length >= limit Craft adds .hidden to it rather than removing it.
{{ forms.elementSelectField({
label: "Related products"|t("my-plugin"),
id: "relatedProducts",
name: "relatedProducts",
elementType: "craft\\commerce\\elements\\Product",
elements: entry.relatedProducts.all(),
viewMode: "list", // list · list-inline · cards · cards-grid · thumbs
limit: 3, // field's maxRelations
selectionLabel: "Add a product"|t("my-plugin"),
sources: ["*"],
}) }}
The search input¶
When a field passes searchCriteria, Craft renders a search box beside the Add button inside a shared .flex row. Without searchCriteria there's no search box at all, which is why some relation fields have one and others don't.
{{ forms.elementSelectField({
label: "Featured exhibits"|t("my-plugin"),
name: "featuredExhibits",
elementType: "craft\\elements\\Entry",
elements: entry.featuredExhibits.all(),
selectionLabel: "Add an entry"|t("my-plugin"),
searchCriteria: { section: "exhibits" },
}) }}
Assets field — thumbnail view¶
Assets fields default to a thumbnail view. Note that viewMode: "large" is a legacy alias Craft remaps to "thumbs" (BaseRelationField::normalizeConfig()), so "thumbs" is the real value. With maxRelations: 1 the field accepts a single image — a common “featured image” setup.
There's no thumbnail template. thumbs reuses the same list as the default view with size: "large" and inline: true, so you get <ul class="elements chips chips-large inline-chips"> — a grid of auto-fill columns, each holding a .chip.large that stacks its 120px thumb above the label. Same chip, different axis.
hero-spring.jpg
{{ forms.elementSelectField({
label: "Featured image"|t("my-plugin"),
id: "featuredImage",
name: "featuredImage",
elementType: "craft\\elements\\Asset",
elements: entry.featuredImage.all(),
viewMode: "thumbs", // thumbnails ("large" is a legacy alias)
limit: 1, // single selection
selectionLabel: "Choose an image"|t("my-plugin"),
sources: ["volume:uploads"],
criteria: { kind: ["image"] },
}) }}
Cards view¶
Set viewMode: "cards" for a richer element that shows a heading plus body content — useful when a plain label isn’t enough to tell related elements apart. Cards are a different template (_elements/cards.twig) and a different renderer (Cp::elementCardHtml()), not a bigger chip.
A card is a .card-titlebar strip — element icon, type label, and actions — sitting over a .card-main grid that holds .card-content (a .card-heading and .card-body) alongside the thumb.
-
Alpine Down JacketOuterwear · $289.00
{{ forms.elementSelectField({
label: "Related products"|t("my-plugin"),
name: "relatedProducts",
elementType: "craft\\commerce\\elements\\Product",
elements: entry.relatedProducts.all(),
viewMode: "cards", // "cards-grid" emits <ul class="card-grid"> instead
}) }}
viewMode: "cards" stacks the cards in a column; "cards-grid" swaps the container class to card-grid — note the class is card-grid, singular, while the view mode is cards-grid. The grid auto-fills at a 300px minimum, against 160px for the thumbs grid.
| Setting | PHP property | Effect |
|---|---|---|
| View mode | viewMode |
list · list-inline · cards · cards-grid · thumbs |
| Limit | maxRelations |
Max related elements; 1 makes it single-select |
| Selection label | selectionLabel |
Custom text on the Add button |
| Sources | sources |
Which sections / volumes / groups can be picked from |
| Show site menu | showSiteMenu |
Let authors relate elements from other sites |
| Maintain hierarchy | maintainHierarchy |
(Structures) keep selections in tree order; pair with branchLimit |
| Preview mode | previewMode (Assets) |
full shows the filename label; thumbs is image-only |
Note
Same UI for custom element types. If your plugin defines its own element, a relation field pointing at it renders with this exact elementSelect. Override your element’s attributeHtml(), getChipLabelHtml(), and getCardBodyHtml() (on craft\base\Element) to control how it appears in each view mode. (There is no chipHtml() / cardHtml() to override — those are static renderers on craft\helpers\Cp.)