Sizes
Five size steps, from xsm to slg.
@for (sizeOption of sizes; track sizeOption) {
<gog-select [label]="'Size: ' + sizeOption" [size]="sizeOption" [options]="frameworks" [(value)]="sizeDemoValue" />
}gog-select
A dropdown select with five sizes, disabled options, placement control, a body-portaled panel, and full ControlValueAccessor support. Once open, Arrow Up/Down move between options, Home/End jump to the first/last, and Escape closes.
Import the component and drop it into a template.
import { SelectComponent } from '@guildofgleks/ui';
@Component({
// ...
imports: [SelectComponent],
})
Basic usage — a labeled field bound with [(value)]:
<gog-select label="Framework" [options]="frameworks" [(value)]="framework" />Selected: None selected
Five size steps, from xsm to slg.
@for (sizeOption of sizes; track sizeOption) {
<gog-select [label]="'Size: ' + sizeOption" [size]="sizeOption" [options]="frameworks" [(value)]="sizeDemoValue" />
} A single option can be marked disabled without disabling the whole field, or the trigger itself can be disabled with [disabled]="true". The last field shows an error computed entirely by the page — gog-select just renders whatever errorMessage it's given, for as long as it's non-empty.
<gog-select label="Disabled" [options]="frameworks" value="angular" [disabled]="true" />
<gog-select label="Plan (one option disabled)" [options]="plansWithDisabled" [(value)]="plan" />
<gog-select
label="Required plan"
placeholder="Choose a plan..."
[options]="plansWithDisabled"
[errorMessage]="requiredValue() === null ? 'Please pick a plan.' : ''"
[(value)]="requiredValue"
/> With errorDisplay="auto" and a [formControl], the field decides for itself when to show the error — once the control has been touched and is invalid — instead of the page computing that timing. Click the field, then click away without choosing anything.
<gog-select
label="Billing cycle"
placeholder="Choose a cycle..."
[options]="billingCycles"
[formControl]="billingCycleControl"
errorMessage="A billing cycle is required."
errorDisplay="auto"
/> Full width of its container by default (see every field above). [fullWidth]="false" shrinks the trigger to fit its selected label instead — useful for a compact field alongside one that should keep growing. The dashed outline is the row both share.
<gog-select label="Country" [options]="countries" [(value)]="fullWidthCountry" />
<gog-select label="Currency" [options]="currencies" [(value)]="currency" [fullWidth]="false" /> Project an <ng-template gogDropdownChevron> to swap the trigger's icon. ariaLabel names a field that has no visible label at all.
<gog-select [options]="sortOptions" [(value)]="sortValue">
<ng-template gogDropdownChevron>
<gog-icon name="sort" />
</ng-template>
</gog-select>
<gog-select
ariaLabel="Country (no visible label)"
placeholder="Pick a country"
[options]="countries"
[(value)]="ariaOnlyValue"
/>appendToBody portals the panel into document.body — useful inside scrollable or overflow-clipped containers, since the panel escapes the clipping. dropdownWidth and dropdownMaxHeight then take any CSS length to override the trigger-derived size; both apply only with appendToBody. The list below has 20 options, capped to 160px so it scrolls internally.
<gog-select
label="Country (fixed 220px / 160px panel)"
[options]="countries"
[appendToBody]="true"
dropdownWidth="220px"
dropdownMaxHeight="160px"
[(value)]="compactPanelValue"
/>optionLabel, optionValue and optionDisabled each take a property path — dot-paths included — or a function. A real DTO goes straight in, with no mapping into { id, name } first and nothing lost on the way back out. The defaults are 'name' / 'id' / 'disabled', so code written before 21.3.0 is unaffected; GogDropdownOption is no longer a requirement, just the shape those defaults expect to find.
Set [optionValue]="null" and the control emits the option object itself — the same reference you passed in, not a copy.
id = null · object = null
<!-- A real DTO goes straight in: no mapping into { id, name } first. -->
<gog-select
label="Assignee"
optionLabel="profile.fullName"
optionValue="uuid"
optionDisabled="suspended"
[options]="users"
[(value)]="userId"
/>
<!-- [optionValue]="null" hands back the option object itself. -->
<gog-select
label="Assignee (object)"
optionLabel="profile.fullName"
[optionValue]="null"
[options]="users"
[(value)]="userObject"
/>filter puts a search box in the panel, matching case-insensitively on the resolved optionLabel. The query resets when the panel closes, and filterPosition sticks the box to either end of the list. filterMatch swaps the default match for your own predicate — useful for searching a field the label never shows.
The search box has square corners, and that is the concentric answer rather than a value clamped to zero. It is inset from the panel edge by exactly the panel's own radius, and at that distance the inner box's corner point sits on the centre of the panel's corner arc — a right angle there is equidistant from the whole curve, which is the only shape that keeps the gap constant. Override --gog-select-panel-radius and the filter reshapes itself to match.
<gog-select
label="Country"
[filter]="true"
filterPlaceholder="Search countries…"
filterEmptyMessage="No country matches"
[options]="manyCountries"
[(value)]="filteredCountry"
/>
<!-- filterMatch replaces the default substring match on the label. -->
<gog-select
label="Assignee"
optionLabel="profile.fullName"
optionValue="uuid"
[filter]="true"
[filterMatch]="matchNameOrRole"
[options]="users"
[(value)]="userId"
/> The clear button is value-driven: it appears once something is selected and vanishes when nothing is. That is the point — it replaces the fake "— not selected —" option teams add to make a choice undoable. It takes the outermost trailing position, with the chevron shifting inward, so the trigger's width stays stable and the destructive control is not on the very edge.
<gog-select label="Plan" [clearable]="true" [options]="plansWithDisabled" [(value)]="plan" /> A gogDropdownOption template replaces one option row. Its context carries the option itself plus selected, disabled and the already-resolved label — so a custom row can decorate the label rather than re-derive it.
<gog-select
label="Assignee"
optionLabel="profile.fullName"
optionValue="uuid"
[options]="users"
[(value)]="slotUserId"
>
<ng-template gogDropdownOption let-user let-label="label" let-selected="selected">
<strong>{{ label }}</strong>
<small>{{ user.profile.role }}</small>
</ng-template>
</gog-select>virtualize[virtualize]="true" renders only the rows in view. Unwindowed, 10 000 options build 10 000 DOM rows to show about six — measured in Chrome, 512 ms before the panel appears, against 21 ms windowed on the same data. The scrollbar looks the same either way, because spacers stand in for the rows that are not there.
Both fields hold the same 10 000 options. Open each and compare how long the panel takes to appear — the difference is the whole feature.
<gog-checkbox label="virtualize the first field" [(checked)]="virtualizeOn" />
<gog-select
label="City (windowed)"
[options]="cities"
[virtualize]="virtualizeOn()"
[filter]="true"
[(value)]="windowedCity"
/>
<gog-select
label="City (eager, for comparison)"
[options]="cities"
[filter]="true"
[(value)]="eagerCity"
/>Off by default, and never switched on at some row count. A windowed list behaves differently in ways nothing about the data predicts: Ctrl+F finds only the rendered rows, and CSS targeting :last-child matches the last rendered row. A threshold would make that depend on how much data happened to arrive, so set it per field, or app-wide with GOG_CONFIG.dropdown.virtualize.
aria-setsize and aria-posinset carry the real list, so a screen reader hears "10 000 items" rather than "20". Arrow keys walk the whole list — End reaches the last option. gog-table's virtualize is a different story under the same name: table rows vary in height, so it measures as it goes — see the Table page. | Name | Type | Default | Description |
|---|---|---|---|
value | string | number | null (model) | null | Two-way bindable selected option id via [(value)]. Also driven by Angular Forms through writeValue/registerOnChange when used with formControlName/[formControl]/ngModel. |
label | string | '' | Field label. |
ariaLabel | string | '' | Accessible name for the field when there is no visible label. |
inputId | string | '' | id on the trigger button, and target of the label's for attribute. |
placeholder | string | 'Select...' | Text shown while no option is selected. |
options | TOption[] | [] | The list of choices — your own objects. GogDropdownOption ({ id, name, disabled? }) is just the shape the default accessors expect, not a requirement. |
optionLabel | string | ((o: TOption) => string) | 'name' | How an option turns into its visible text: a property path (dot-paths included, "profile.fullName") or a function. |
optionValue | string | ((o: TOption) => unknown) | null | 'id' | How an option turns into the emitted value. Set it to null and the control emits the option OBJECT itself — the same reference you passed in. |
optionDisabled | string | ((o: TOption) => boolean) | 'disabled' | Which options cannot be picked. |
clearable | boolean | GOG_CONFIG.control.clearable ?? false | Adds a clear button in the outermost trailing position, with the chevron shifting inward when it appears. It shows only once something is selected — which is what removes the need for a fake "— not selected —" option just to make a choice undoable. |
clearAriaLabel | string | 'Clear selection' | Accessible name for that clear button. |
filter | boolean | GOG_CONFIG.dropdown.filter ?? false | Puts a search box in the panel, matching case-insensitively on the resolved optionLabel. The query resets when the panel closes. |
filterPosition | 'top' | 'bottom' | GOG_CONFIG.dropdown.filterPosition ?? 'top' | Which end of the panel the search box sticks to. It carries a divider on the side facing the list, so it reads as chrome rather than as a row. |
filterPlaceholder / filterEmptyMessage | string | 'Search...' / 'No matches' | Wording for the search box and for the empty result. |
filterMatch | ((option: TOption, query: string) => boolean) | null | null | Replaces the default case-insensitive substring match — for searching a field the label does not show, or for fuzzy matching. |
floatLabel | 'none' | 'in' | 'on' | 'over' | GOG_CONFIG.floatLabel.variant ?? 'none' | Rests the label inside the field like a placeholder and floats it up once something is selected or the field has focus. |
floatLabelShowPlaceholder | boolean | GOG_CONFIG.floatLabel.showPlaceholder ?? false | Reveals the placeholder once the label has floated out of the way. |
errorMessage | string | '' | Error text to display. Visibility is governed by errorDisplay. |
errorDisplay | 'auto' | 'manual' | GOG_CONFIG.control.errorDisplay ?? 'manual' | 'manual': shown for as long as errorMessage is non-empty — you decide the timing. 'auto': shown once the attached FormControl is touched and invalid; falls back to manual without one. |
size | 'xsm' | 'sm' | 'md' | 'lg' | 'slg' | GOG_CONFIG.control.size ?? 'md' | Field height, padding, and font size. |
disabled | boolean | false | Disables the trigger and closes the panel if it is open. |
fullWidth | boolean | true | Fills its container by default. Set false to shrink to fit the selected label instead. |
minWidth | string | null | null (--gog-select-min-width, 120px) | Floor for an auto-width trigger, any CSS length — so a short selection cannot collapse the field to its own chrome. |
dropdownDirection | 'auto' | 'up' | 'down' | GOG_CONFIG.dropdown.direction ?? 'auto' | Which side the panel opens on. 'auto' flips to whichever side has room in the viewport. |
dropdownZIndex | number | null | null | Explicit stacking order for the panel. Left unset it falls back to the --gog-dropdown-z token. |
dropdownWidth | string | null | null | Fixed panel width, any CSS length. Applies only with appendToBody. Left unset, the panel sizes to its own content with the trigger width as a floor, capped by --gog-{select,multiselect}-panel-max-width — so picking a short option no longer cuts the longer ones off the list. |
dropdownMaxHeight | string | null | null | Fixed panel max-height, any CSS length. Applies only with appendToBody. |
appendToBody | boolean | GOG_CONFIG.dropdown.appendToBody ?? false | Portals the panel into document.body instead of rendering it inline — escapes an ancestor's scroll/overflow clipping. Worth setting app-wide for a layout whose dropdowns generally live inside scrollable containers. |
ripple | boolean | undefined | undefined | Press ripple on each option row in the panel. Unset, falls back to GOG_CONFIG.ripple.enabled, which is off by default; setting it here wins over the app-wide value in both directions. |
virtualize | boolean | undefined | GOG_CONFIG.dropdown.virtualize ?? false | Renders only the option rows in view — about twenty in the DOM whatever the list holds. Off by default and never switched on at a row count: Ctrl+F finds only rendered rows and :last-child matches the last rendered one. aria-setsize/aria-posinset keep the announced count real. |
| Directive | Context | Description |
|---|---|---|
gogDropdownOption | $implicit, selected, disabled, label | Replaces one option row. |
gogDropdownChevron | — | Replaces the trigger's chevron. |
Set any of these once with provideGogConfig() instead of repeating them on every instance — an instance's own input still wins when it sets one itself. See Global Configuration for the full reference.
GOG_CONFIG.control.sizeGOG_CONFIG.control.errorDisplayGOG_CONFIG.control.clearableGOG_CONFIG.floatLabel.variantGOG_CONFIG.floatLabel.showPlaceholderGOG_CONFIG.dropdown.appendToBodyGOG_CONFIG.dropdown.directionGOG_CONFIG.dropdown.virtualizeGOG_CONFIG.dropdown.filterGOG_CONFIG.dropdown.filterPositionGOG_CONFIG.ripple.enabled — on the panel’s optionsGOG_CONFIG.labels.clearSelectionThis site is not on the default here. The library ships ripple.enabled as false; these docs set it to true app-wide so the demos above actually show the press feedback. In a fresh app you get no ripple until you ask for one — the droplet button in the header switches this site between the two, and it is on right now.
Every CSS custom property the select paints with. Override any of them — on a single instance, a subtree, or a theme — to restyle it. See the Theming guide for the full token-layering model, or the Theme Generator to tweak these live.
| Token | Description |
|---|---|
--gog-select-label-color | Field label color. |
--gog-select-field-bg / -field-border | Field surface and border. |
--gog-select-radius | The field’s corner radius — the field only. Until 21.12.0 it shaped the dropdown panel too, so rounding the trigger into a pill rounded the overlay into one as well; that box has its own token now. |
--gog-select-focus-border / -focus-ring | Focus state. |
--gog-select-panel-bg / -panel-shadow / -panel-max-width | Dropdown panel surface, and the cap on a panel that sizes to its own content rather than to the trigger. |
--gog-select-panel-gap | Gap between the trigger and the panel. It was --gog-select-panel-offset until 21.13.0, and the old name stopped resolving in 21.14.0. |
--gog-select-option-height | A seed for the first frame, not the row height: the component measures a real row and corrects itself, which is also what decides whether the panel opens up or down. |
--gog-select-panel-radius | The panel’s own corner, new in 21.12.0 and defaulting to var(--gog-radius), so nothing moves unless you change it. gog-autocomplete and gog-datepicker already declared theirs; this and the multiselect’s are the pair that completes the family. |
--gog-select-options-padding / --gog-select-option-radius | The gutter around the option list and the row’s own corner, matching what gog-autocomplete and gog-multiselect already had. This panel had no interior at all before 21.12.0, which is why its first and last rows were square inside a rounded corner. The row radius derives from the panel radius less the gutter, so it stays concentric once --gog-density moves the padding. |
--gog-select-min-width | The floor an auto-width trigger cannot collapse past (120px). |
--gog-select-chevron-color / -chevron-inset | Dropdown arrow color and inset. Since 21.3.0 the inset lands on --gog-control-icon-offset, the same line as gog-inputfield’s icons — the three controls now line up in a form. |
--gog-select-option-hover-bg / -option-press-bg / -option-selected-color | Option row states. |
--gog-select-float-label-reserve / -in-top / -on-bg / -over-gap / -over-reserve | Float-label geometry, derived from the shared --gog-field-float-label-* scale. |