@guildofgleks/ui
v21.6.0
Published
A lightweight component library for Angular 21 and 22 — standalone, signal-based, OnPush, RTL-ready, with no CDK or Material dependency.
Downloads
1,961
Maintainers
Readme
@guildofgleks/ui
An Angular 21 and 22 component library with no CDK and no Material. 29 components, 5 directives and 3 services, all standalone, all signal-based, themed entirely through CSS custom properties.
npm install @guildofgleks/uiWhy this one
- Small dependency footprint. Peers are
@angular/core,@angular/common,@angular/formsand@angular/platform-browser. No router, no CDK, no animations package.tslibis the only runtime dependency. - Signals throughout.
input()/output()/model(),OnPusheverywhere, no NgModules. - Themeable without a build step. Every value a component paints with is a
--gog-*custom property. Swap a palette, restyle one component, or override a single instance — no Sass variables, no JS theme object. - Reactive Forms native. Every form control is a
ControlValueAccessorbuilt and tested against[formControl]/formControlName. - Your data, your shapes. Dropdowns take your objects with accessor paths
(
optionLabel="profile.fullName"), not a mandated{ id, name }DTO. - Accessible by default. Keyboard navigation, ARIA wiring and generated label associations come with the components rather than with extra attributes.
- Right-to-left included.
dir="rtl"on<html>or on any wrapper mirrors every component, portaled overlays included. Nothing to configure per component, no second stylesheet.
Setup
Install it with whichever package manager you use — or with ng add, which installs it and does
step 1 for you:
npm install @guildofgleks/ui
# or
yarn add @guildofgleks/ui
# or — also does step 1 below
ng add @guildofgleks/uiSteps 2 and 3 are yours either way: a schematic can't know where in your app you want components or dialog and toast hosts.
1. Add the stylesheet. It carries the baseline theme and the utility classes the components use — without it they render unstyled.
// angular.json → projects.<app>.architect.build.options
"styles": [
"node_modules/@guildofgleks/ui/styles/index.css",
"src/styles.scss" // yours, after the baseline so it wins
]It brings its own box-sizing: border-box, scoped to the elements the library renders, so the
components size correctly whether or not your app has a global reset — since 21.6.0. Your own
reset is untouched either way, and a single class of specificity means your own styles still win.
2. Import components where you use them — each is standalone:
import { ButtonComponent, SelectComponent } from '@guildofgleks/ui';
@Component({
imports: [ButtonComponent, SelectComponent],
template: `
<gog-select label="Region" [options]="regions" [(value)]="region" />
<gog-button (gogClick)="save()">Save</gog-button>
`,
})
export class ExampleComponent {}Outputs are prefixed gog (gogClick, gogToggle) so they never collide with native DOM
events. Inputs keep their natural names.
3. If you use dialogs or toasts, place their hosts once. DialogService.open() and
ToastService.show() update state but render nothing without them:
@Component({
selector: 'app-root',
imports: [DialogComponent, ToastContainerComponent],
template: `
<router-outlet />
<gog-dialog />
<gog-toast-container />
`,
})
export class App {}One <gog-dialog /> hosts every dialog (they stack); one <gog-toast-container /> hosts all
four toast corners.
Right-to-left
RTL is supported. Set dir="rtl" on <html> (or on any subtree) and every component
mirrors: stylesheets use logical properties, portaled panels and tooltip bubbles copy a scoped
dir onto themselves, a tooltip's position="auto" prefers the mirrored horizontal side, and
the calendar's month arrows turn around.
Two things stay physical on purpose, because they are physical words in the API: a tooltip's
explicit position="left"/"right", and a toast's top-left/top-right/bottom-left/
bottom-right corner. "auto" is the direction-aware tooltip placement; pick the corner you
want for a toast.
Theming
Every value the components paint with lives in styles/theme.css, in three layers:
Foundation — palette, type scale, spacing, motion. Override these to restyle everything at once; component tokens derive from them, so a palette swap carries through on its own.
Component — --gog-<component>-*, one block per component, named after the component you
write in markup (gog-button → --gog-button-*), to restyle a single component app-wide:
:root[data-theme='mine'] {
--gog-button-font-family: var(--gog-font-body);
--gog-button-ghost-hover-bg: color-mix(in srgb, var(--gog-accent-color) 20%, transparent);
--gog-table-hover-bg: var(--gog-hover-color);
}Instance — a small set left deliberately undeclared, so setting one anywhere beats the variant and size classes:
.my-form gog-button {
--gog-button-bg: rebeccapurple; /* wins over .gog-btn--primary */
}Renamed in 21.5.0. Three prefixes were abbreviated and are now spelled out:
--gog-btn-*→--gog-button-*,--gog-confirm-*→--gog-confirmation-dialog-*, and--gog-ms-*→--gog-multiselect-*(that one since 21.3.0). The old spellings still work — every new name derives from its old twin — and are removed in 21.7.0. A CSS override that stops being read fails silently, which is why the window is two minors rather than one.One prefix that looks abbreviated and is not:
--gog-input-*. It names the shared text-field block that bothgog-inputfieldandgog-textarearender (.gog-input__field), not thegog-inputfieldcomponent — the two are meant to restyle together from one token set, so there is no--gog-inputfield-*and there will not be one.
Every group and token name is in TOKENS.md, generated from theme.css so it
cannot drift, and available at runtime as GOG_TOKEN_GROUPS.
Light, dark and your own
The active theme is a data-theme attribute on :root, managed by ThemeService:
private readonly theme = inject(ThemeService);
this.theme.toggleTheme(); // light ⇄ dark
this.theme.setTheme('cyberpunk'); // any name you declared in CSSOut of the box it adopts whatever data-theme is already on the document, or light.
Persisting the choice and following the OS setting are opt-in:
provideGogConfig({
theme: { storageKey: 'app-theme', followSystem: true, darkTheme: 'cyberpunk' },
});A theme only declares what it changes — the derived layer re-resolves against whatever palette is
in scope, so a new palette restyles every component without listing any of them. The slate
preset is the worked example, palette-only:
@import '@guildofgleks/ui/styles/index.css';
@import '@guildofgleks/ui/styles/presets/slate.css';Fonts are left alone on purpose (system stacks, no webfont download). Add
@guildofgleks/ui/styles/fonts.css for the showcase's typography.
App-wide configuration
Anything visual is a token. Everything else — the settings you would otherwise repeat on every instance — goes through one provider:
provideGogConfig({
control: { size: 'sm', errorDisplay: 'auto', clearable: true },
dropdown: { appendToBody: true },
datepicker: { locale: 'de-DE', format: 'dd.MM.yyyy' },
labels: { clear: 'Очистить', selectAll: 'Выбрать все' }, // translate the library once
});Keys: control, dropdown, floatLabel, datepicker, autocomplete, inputfield, textarea,
tooltip, scroll, button, paginator, toast, theme, labels. An instance's own input
always wins, and providing the config again lower in the injector tree layers onto the parent
rather than replacing it.
Icons work the same way — 41 Lucide glyphs ship with the package, and your own register by name:
provideGogIcons({ cart: '<svg viewBox="0 0 24 24">…</svg>' });<gog-icon name="cart" /> <gog-tag iconName="cart">In basket</gog-tag>Overlays and the viewport
Three things this library renders cover the viewport with position: fixed:
<gog-dialog />'s backdrop, <gog-toast-container />, and <gog-spinner [overlay]="true" />.
That is true only while nothing above them establishes a containing block. contain,
transform, filter, backdrop-filter and will-change on any ancestor silently retarget
a fixed element to that ancestor's box — a CSS rule with no error and no warning, and the usual
first sighting is "my modal only dims half the page".
It is not hypothetical here: gog-scroll sets contain: layout style, so a dialog opened
from inside a scroller dims the scroller, and a toast container nested in one corners its toasts
against the scroller. Two rules keep it simple:
- Place the dialog and toast outlets in your root component, not inside the section that happens to use them. They are singletons anyway — one of each renders everything.
- A spinner overlay covers whatever contains it, which is often what you want inside a card. For a genuinely full-screen one, render it at the root too.
The dropdown panels (gog-select, gog-multiselect, gog-autocomplete, gog-datepicker) and
gog-menu avoid the whole question by rendering into <body> — appendToBody for the
dropdowns, always for the menu.
Components
| Group | Components |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Form controls | gog-inputfield, gog-textarea, gog-select, gog-multiselect, gog-autocomplete, gog-checkbox, gog-radio-group, gog-toggle, gog-slider, gog-datepicker, gog-calendar, gog-button-toggle-group |
| Actions | gog-button, gog-chip |
| Data | gog-table (+ gog-column), gog-paginator, gog-tag |
| Layout & disclosure | gog-accordion, gog-tabs (+ gog-tab), gog-collapsible, gog-divider, gog-scroll |
| Overlays | gog-dialog, gog-confirmation-dialog, gog-toast (+ gog-toast-container), gog-menu (+ gogMenuTrigger / gogMenuItem) |
| Feedback | gog-spinner, gog-spinner-overlay, gog-progressbar, gog-skeleton |
| Content | gog-icon |
Directives: gogButton (a link that looks like a button), gogTooltip, gogBadge,
gogCollapsibleTrigger, gogCollapsibleContent.
Services: DialogService, ToastService, ThemeService.
Seventeen more directives go on markup you own rather than configuring a component through an
input — slots like gogColumnBody, gogInputAddonStart and gogDropdownOption, and the menu's
gogMenuTrigger / gogMenuItem.
A few things worth knowing before you reach for a workaround:
gog-tableworks two ways. By default it owns the data and sorts and pages in memory. With[lazy]="true"it hands both to the server:valueis the current page,totalRecordsdrives the paginator, andgogSortChange/gogPageChangeare your refetch signals. Row selection isselectionMode+[(selection)]; setdataKeyor a refetch drops it.gog-buttoncannot be a link — it renders its own<button>. Use[gogButton]on your own<a>instead; nothing is brokered through inputs, sorouterLink,hrefandtargetkeep working. That is also why this package needs no@angular/router.gog-inputfieldandgog-textareaforward the native attribute space they wrap —readonly,maxlength,pattern,inputMode,spellcheckand the text-fieldtypevalues. They also generate their ownid, so labels and error messages are wired up withoutinputId.gog-collapsibleis headless — no markup of its own. Project any element as the trigger and any element as the panel.[(ngModel)]is untested. The library never importsFormsModule; use Reactive Forms.
Documentation
| | |
| ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| AGENTS.md | the full API reference — every input, output, slot, type and default, per component. Ships in this package. |
| TOKENS.md | every --gog-* token, generated from theme.css |
| CHANGELOG | release history |
AGENTS.md is written for an AI coding assistant working in your project, but it is the most
complete API reference either way — point your assistant at it and it will stop guessing.
License
Apache-2.0 © Roman Malitskyi
Built-in icons are Lucide glyphs, inlined so the package keeps zero runtime
dependencies. Lucide is ISC licensed; portions are held by Cole Bemis 2013–2022 as part of
Feather (MIT), all others by Lucide Contributors 2022 — full notice in
src/lib/shared/icons.ts.
