Global Configuration
Some inputs are things a whole app decides once — a house style for how long a scrollbar stays visible, whether every form field shows its clear button, which locale date pickers use — rather than something to repeat on every instance.
provideGogConfig(...) is the one place to set them. There is no separate injection token per
component per setting.
import { bootstrapApplication } from '@angular/platform-browser';
import { provideGogConfig } from '@guildofgleks/ui';
bootstrapApplication(App, {
providers: [
provideGogConfig({
control: { size: 'sm', errorDisplay: 'auto', clearable: true },
dropdown: { appendToBody: true, filter: true },
datepicker: { locale: 'de-DE', firstDayOfWeek: 1, format: 'dd.MM.yyyy' },
toast: { position: 'top-right', duration: 4000 },
}),
],
});
Precedence
For every configurable input the rule is the same, in this order:
- the instance's own input, when it is set;
- the value from
GOG_CONFIG; - the component's built-in default.
So a global control.size: 'sm' makes every form control small, and a single
<gog-select size="lg"> is still large. Nothing you set here takes a choice away from a call
site.
It merges down the injector tree
A nested provideGogConfig(...) — in a route's or a component's providers — layers onto the
parent's config rather than replacing it. Merging is one level deep, per component key, with the
nearest provider winning field by field.
// app.config.ts
provideGogConfig({
button: { debounce: 500 },
tooltip: { position: 'top', showDelay: 300 },
});
// a route's providers — button.debounce still applies inside this route,
// and tooltip.position stays 'top'. Only showDelay changes.
provideGogConfig({ tooltip: { showDelay: 0 } });
Changed in 21.3.0. Before this, a nested call silently dropped every key it did not restate — a route setting only
{ tooltip: … }lost the app-widebutton.debouncewith no error anywhere. If you were working around that by repeating the whole config at each level, those repeats are now redundant but harmless.
To drop an inherited value rather than change it, set it back to the component's own default explicitly.
What you can configure
| Key | Fields | Applies to |
|---|---|---|
control |
size, errorDisplay, clearable |
Not every field applies to every control. size: button, input field, textarea, select, multiselect, checkbox, radio group, button toggle group, datepicker. errorDisplay: input field, textarea, select, multiselect, autocomplete, radio group, slider, datepicker (not button or checkbox). clearable: input field, textarea, select, multiselect, autocomplete, datepicker. Deliberately not table, accordion or paginator, whose size means row density and whose defaults differ, nor spinner, skeleton, tag, chip or toggle. |
dropdown |
appendToBody, direction, filter, filterPosition, virtualize 21.13.0 |
gog-select and gog-multiselect. gog-datepicker and gog-autocomplete also honour appendToBody and direction for their panels, but not filter/filterPosition (neither has a separate search box). virtualize reaches the three dropdowns — select, multiselect and autocomplete — and not gog-table, whose own virtualize has no app-wide key. |
floatLabel |
variant, showPlaceholder |
The field controls: input field, textarea, select, multiselect, autocomplete and datepicker. |
datepicker |
locale, firstDayOfWeek, format |
gog-datepicker and gog-calendar. The calendar resolves these itself — it does not need them passed down from a datepicker. |
autocomplete |
searchDebounce, minLength, openOnFocus 21.3.1 |
gog-autocomplete. openOnFocus is on by default. |
tooltip |
position, showDelay, hideDelay |
The gogTooltip directive. |
scroll |
autoHide, hideDelay, size, overscrollBehavior, showTrack 21.3.1, horizontalWheel 21.9.0 |
gog-scroll, and every component that uses one internally. |
spinner 21.9.0 |
component, variant |
Every spinner the library draws: gog-spinner, gog-spinner-overlay, the one gog-button shows while loading, gog-autocomplete's while it searches, and the one gog-table puts in place of its rows. The last three have no spinner input of their own, which is the reason the key exists. component takes your own component and renders it inside the library's size wrapper — see below. The overlay honoured neither key until 21.10.0. |
button |
debounce |
gog-button, and the [gogButton] directive. |
ripple 21.6.1 |
enabled |
The press ripple on gog-button, [gogButton], gog-button-toggle-group, gog-chip, gog-tabs, gog-accordion, gogCollapsibleTrigger, gogMenuItem and the gog-select/gog-multiselect/gog-autocomplete options — nine surfaces at once. Off by default, so 21.6.1 changed the look of nothing. Each of those takes a ripple input that wins over this in both directions. Not the gogRipple directive: writing that attribute is already the per-element decision. |
inputfield 21.3.1 |
showSpinButtons |
gog-inputfield. Whether a type="number" field shows the library's own spin buttons instead of the browser's. On by default. |
textarea 21.3.1 |
resize |
gog-textarea. Which direction(s) the drag handle resizes in — the native CSS resize value space. 'vertical' by default. |
paginator 21.4.0 |
showPageSizeSelect, pageSizeOptions |
gog-paginator, and through it gog-table's built-in pagination. The rows-per-page select is off by default; the options default to [10, 20, 30, 40, 50]. |
toast |
position, duration |
ToastService. |
labels 21.3.2 |
every fixed string the library renders — see below | Input field, textarea, select, multiselect, autocomplete, datepicker, calendar, paginator, table, DialogService, ToastService. |
theme 21.3.2 |
storageKey, defaultTheme, followSystem, lightTheme, darkTheme |
ThemeService — see below. Every field is off or neutral by default, so an app that configures nothing keeps the pre-21.3.2 behaviour. |
labels — translating the library
Every string a component renders that you never write markup for: a clear button's accessible name, the paginator's page buttons, the calendar's "Today". They live here rather than as one input per string for the reason this file exists at all — a Russian-language app relabels "Clear" once, not on all 340 fields.
provideGogConfig({
labels: {
clear: 'Löschen', // input field / textarea clear button
clearSelection: 'Auswahl löschen', // select / multiselect / autocomplete
clearDate: 'Datum löschen', // datepicker
selectAll: 'Alle auswählen', // multiselect panel — visible text, not just a label
clearAll: 'Alle löschen', // multiselect panel
increment: 'Erhöhen', // number spin buttons
decrement: 'Verringern',
showPassword: 'Passwort anzeigen',
hidePassword: 'Passwort verbergen',
closeDialog: 'Schließen',
closeToast: 'Schließen',
closeAlert: 'Meldung schließen', // gog-alert's dismiss button
pagination: 'Seitennavigation',
previousPage: 'Vorherige Seite',
nextPage: 'Nächste Seite',
openCalendar: 'Kalender öffnen',
rowsPerPage: 'Zeilen pro Seite', // gog-paginator's size select
total: 'Gesamt', // gog-table's row-count label
tablePagination: 'Tabellennavigation',
selectRow: 'Zeile auswählen',
selectAllRows: 'Alle Zeilen auswählen',
today: 'Heute',
thisMonth: 'Aktueller Monat',
previousMonth: 'Vorheriger Monat',
nextMonth: 'Nächster Monat',
previousYear: 'Vorheriges Jahr',
nextYear: 'Nächstes Jahr',
hours: 'Stunden',
minutes: 'Minuten',
seconds: 'Sekunden',
togglePanel: 'Bereich umschalten', // gog-panel's toggle, when the panel has no header
},
});
Five of those — rowsPerPage, total, tablePagination, selectRow, selectAllRows — arrived
with the table's selection and the paginator's size select
21.4.0.
labels.page takes a function, not a string
The paginator's per-page button names ("Go to page 4", "Page 4, current page") interpolate the page number, so this one field is a formatter:
provideGogConfig({
labels: {
page: (page, isCurrent) => (isCurrent ? `Seite ${page}, aktuell` : `Zu Seite ${page} wechseln`),
},
});
It is the only non-string field in labels, and deliberately so: a template string with a {0}
placeholder would be a second, weaker formatting language to learn — one that also cannot express
languages where the number's position or the grammar around it depends on its value.
What is not in labels
Strings that describe one control rather than library chrome: gog-checkbox's ariaLabel,
gog-button's ariaLabel, any field's label or placeholder. Those differ per instance by
definition and have no meaningful app-wide value.
Where a per-instance label input exists (clearAriaLabel, todayLabel, selectAllLabel, …) it
still wins for that one control — the usual instance → config → default order.
theme — persistence and the OS setting
ThemeService reads its own key here. Everything is off or neutral by default: with nothing
configured it adopts whatever data-theme is already on <html>, else 'light', and persists
nothing.
provideGogConfig({
theme: {
storageKey: 'my-app-theme', // unset ⇒ nothing is persisted, the theme resets on reload
defaultTheme: 'light', // applied when nothing else decides
followSystem: true, // fall back to prefers-color-scheme, and keep following it
lightTheme: 'one-light', // what toggleTheme() alternates between, and what
darkTheme: 'one-dark', // followSystem maps the OS setting to
},
});
followSystem keeps tracking the OS setting until the app calls setTheme or toggleTheme —
an explicit choice ends the following. It is off by default because switching it on changes which
theme an existing app opens in.
Icons are configured separately
The icon registry is not part of GOG_CONFIG. provideGogIcons(...) is its own app-wide
provider, so don't look for an icons key here:
providers: [provideGogConfig({ control: { size: 'sm' } }), provideGogIcons({ logo: '<svg …>' })];
See the Icon page for the registry, how a registered name overrides a built-in one, and the security rule for the SVG you pass in.
What does not belong here
Anything visual. Colors, radii, spacing and durations are CSS custom properties — see
Theming. A consumer overriding --gog-scroll-thumb-bg at :root already
gets it everywhere, with no TypeScript involved.
GogGlobalConfig exists only for inputs a component reads in TypeScript, where a CSS token cannot
reach: a setTimeout duration, an RxJS throttle window, a locale string. If a value only ever ends
up as a bound style, it belongs in theme.css.
The first exception — ripple.enabled 21.6.1
ripple.enabled is the first visual default that lives here rather than in theme.css, and it
breaks the rule above on purpose. A token could hide the wash — --gog-ripple-opacity: 0 — but the
element would still be created on every press, the pointer listeners would still be attached, and
the browser would still be handed an animation to run. That is a switch that looks off and costs
what being on costs.
A real "off" has to reach the TypeScript, so that a component with the ripple disabled attaches no
listeners and adds no class at all. Everything about how the ripple looks is still a token: the
five --gog-ripple-* properties on the Ripple page.
The second — spinner.component 21.9.0
Every other key here is a value: a number, a string, an enum member. spinner.component is a
component, and it is the one place GOG_CONFIG carries markup.
It is here because of what it removes. A house loading indicator otherwise has to be passed into
every control that can show one — and cannot be passed into the ones that draw their own, which is
most of them. gog-button while loading, gog-autocomplete while it searches and gog-table in
place of its rows all render a spinner that no input of yours reaches. Set this once and all of
them are yours:
provideGogConfig({ spinner: { component: HouseLoaderComponent } });
Your component is rendered through NgComponentOutlet inside the library's own size wrapper,
so it keeps the sizing, the overlay behaviour, role="status" and the accessible name. Only the
visual is yours — which is also why this is not the same thing as variant="custom", the
per-instance version that projects content into one spinner.
Precedence runs the library's usual way, with one wrinkle worth stating out loud: an instance's own
variant wins over both keys here, component wins over variant, and the built-in runic is the
fallback. So <gog-spinner variant="ring"> is still a ring in an app that has set a component — a
default does not overrule something asked for explicitly.
That last rule used to have a catch, and it is worth knowing which version fixed it.
gog-spinner-overlay binds a variant of its own to the spinner it wraps, and until 21.10.0
that input defaulted to 'runic' rather than to nothing — so the spinner inside read it as an
instance asking for the built-in look, and correctly let it win. An app that set a house spinner
got it everywhere except on the one component reached for to cover a whole loading region. From
21.10.0 the overlay's variant is unset by default: ask for nothing and nothing is forwarded, so
this key reaches the overlay like any other spinner.