@northtech/climbalong-ui
v0.0.8
Published
Climbalong's Angular components and Tailwind CSS theme.
Readme
Climbalong UI
Climbalong's Angular components and Tailwind CSS v4 theme. The design follows the spectator site: its palette, Inter and Graphik typography, 4px control corners and component behavior.
| Component | API |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Button | button[caBtn], a[caBtn] (CaButton) |
| Select | <ca-select> (CaSelect, CaOptionTemplate, CaSelectedTemplate) |
| Multi select | <ca-multi-select> (CaMultiSelect) |
| Autocomplete | <ca-autocomplete> (CaAutoComplete) |
| Segmented control | <ca-segmented-control> (CaSegmentedControl) |
| Text input | input[caTextInput] (CaTextInput) |
| Number input | input[caNumberInput] (CaNumberInput) |
| Input number | <ca-input-number> (CaInputNumber) |
| Input group | <ca-input-group>, <ca-input-group-addon> (CaInputGroup, CaInputGroupAddon) |
| Textarea | textarea[caTextarea] (CaTextarea) |
| Checkbox | input[caCheckbox] (CaCheckbox) |
| Radio | input[caRadio] (CaRadio) |
| Switch | input[caSwitch] (CaSwitch) |
| Switch values | input[caSwitch][trueValue], input[caSwitch][falseValue] (CaSwitchValues) |
| Slider | <ca-slider> (CaSlider) |
| Tag | <ca-tag> (CaTag) |
| Badge | <ca-badge>, [caBadge] (CaBadge, CaBadgeOverlay) |
| Avatar | <ca-avatar>, <ca-avatar-group> (CaAvatar, CaAvatarGroup) |
| Divider | <ca-divider> (CaDivider) |
| Skeleton | <ca-skeleton> (CaSkeleton) |
| Loader | <ca-loader> (CaLoader) |
| Message | <ca-message> (CaMessage) |
| Toast | <ca-toaster> (CaToaster), toast() |
| Card | [caCard] (CaCard) |
| Accordion | <ca-accordion>, <ca-accordion-item> (CaAccordion, CaAccordionItem) |
| Tooltip | [caTooltip] (CaTooltip) |
| Popover | <ca-popover>, button[caPopoverTriggerFor] (CaPopover, CaPopoverTrigger) |
| Dialog | <ca-dialog>, ng-template[caDialogHeader] (CaDialog, CaDialogHeader) |
| Drawer | <ca-drawer>, ng-template[caDrawerHeader] (CaDrawer, CaDrawerHeader) |
| Menu | <ca-menu>, [caMenuTriggerFor] (CaMenu, CaMenuTrigger, CaMenuItem) |
| Nav menu | <ca-nav-menu>, <ca-nav-menu-item>, ng-template[caNavMenuContent], a[caNavMenuLink] (CaNavMenu, CaNavMenuItem, CaNavMenuContent, CaNavMenuLink) |
| Split button | <ca-split-button> (CaSplitButton) |
| Tabs | <ca-tabs>, <ca-tab-list>, [caTab], <ca-tab-panel>, <ca-tab-router-panel> (CaTabs, CaTabList, CaTab, CaTabPanel, CaTabContent, CaTabRouterPanel) |
| Color picker | <ca-color-picker> (CaColorPicker) |
| Date picker | <ca-date-picker> (CaDatePicker) |
| Table | <ca-table>, ng-template[caTableHeader\|caTableBody\|caTableFooter\|caTableEmpty], th[caSortableColumn], tr[caSelectableRow], <ca-table-checkbox>, <ca-table-header-checkbox> (CaTable and friends) |
| Row expansion | ng-template[caTableRowExpansion], button[caRowToggler] (CaTableRowExpansion, CaRowToggler) |
| Paginator | <ca-paginator> (CaPaginator) |
Components are added as applications need them.
Text inputs also support native types such as email, password, search, tel, url,
date, and time. The other form directives style native controls, so labels, form submission,
keyboard behavior, and number constraints continue to work. Checkbox and switch use
type="checkbox"; radio uses type="radio"; number uses type="number".
Develop
nvm use
npm ci
npm startOpen http://127.0.0.1:4311, or set another port with PORT=4321 npm start. The playground documents each component and the foundations, with search (⌘K or /), live examples and an API reference generated from api.json. npm test builds the playground and runs the browser suite.
Install
npm install @northtech/climbalong-uiThe package requires Angular 21.2 or later (core, common, forms and router), Angular CDK, @ng-icons/core with @ng-icons/phosphor-icons, ngx-sonner v3.1, and Tailwind CSS v4.
Import the theme after Tailwind in your global stylesheet:
@import 'tailwindcss';
@import '@northtech/climbalong-ui/theme.css';The theme registers the package's compiled components as a Tailwind source. Your build therefore generates the utility classes the components use, together with your own. It also includes the CDK overlay styles.
Load the bundled Inter font through Angular's styles option, so Angular resolves the font file. Graphik is not bundled; set it up in your project as described in Fonts.
"styles": ["node_modules/@northtech/climbalong-ui/fonts.css", "src/styles.css"]Import components individually:
import { Component, signal } from '@angular/core';
import { CaButton, CaSelect, type SelectOption } from '@northtech/climbalong-ui';
@Component({
selector: 'app-results-filter',
imports: [CaButton, CaSelect],
template: `
<ca-select label="Round" size="large" [options]="rounds" [(value)]="round" />
<button caBtn type="button" size="large" [loading]="saving()">Save</button>
`,
})
export class ResultsFilter {
readonly rounds: SelectOption<string>[] = [
{ label: 'Qualification', value: 'qualification' },
{ label: 'Final', value: 'final' },
];
readonly round = signal<string | null | undefined>('qualification');
readonly saving = signal(false);
}API conventions
severityis a semantic colour:primary,secondary,info,success,warn,errororcontrast(the exportedSeveritytype). Badge, message and tag use it; tag also hasbasic. Card keepstone, because it colours a surface.variantis a visual style: button (primary,outlined,ghost…), message (defaultorsimple), loader (spinnerordots), tab list (underline,pillsorsegmented) and accordion item.shapeisrectangleorcircle(button, avatar, skeleton). A pill tag isrounded.labelis always the accessible name. Avatars showinitials; buttons take the nativearia-label.orientationishorizontalorvertical(divider, slider, segmented control).- Text templates use named placeholders, such as
{count}inselectedItemsLabeland{first},{last}and{totalRecords}in the paginator's page report. inputIdsets the id of the focusable element, for an external<label for>.button[caBtn]andth[caSortableColumn]are components, so a template reference on them is the component instance rather than the element.CaButtonhasnativeElementandfocus(); use{ read: ElementRef }inviewChildfor anElementRef. Other attribute selectors are directives, where a plain#refis the element.- Component defaults are CSS recipes in the
componentslayer (.ca-tag,.ca-message…), so any Tailwind utility you put on a component overrides them:<ca-divider class="my-8" />,<ca-tag class="bg-brand-500">. Popovers, tooltips (caTooltipClass) and menus (panelClass, itemstyleClass) apply your classes to their panel the same way. Classes are not merged at runtime, so the library has notailwind-mergedependency.
Date picker
CaDatePicker has no date library: it uses Date and Intl.DateTimeFormat (locale). dateFormat tokens are d/dd, m/mm, M/MM (month name), yy/yyyy (four-digit year) and y (two-digit year). selectionMode is single, range or multiple; showTime adds hours and minutes, and timeOnly inline is a standalone time picker bound to a Date. The calendar header has month and year selects next to the previous and next month buttons; years run from minDate to maxDate, or 100 years either side of the shown year. Typed or pasted text is parsed on Enter or when focus leaves the input. Text that is not an available date in dateFormat is discarded: the value and the input both show the previous date again, however quickly the field is left. An empty input sets the value to null.
Toasts
Add CaToaster to your root component's imports and render <ca-toaster /> once. Call the exported toast function from any component:
import { CaToaster, toast } from '@northtech/climbalong-ui';
toast.success('Registration confirmed.');
toast.error('Results could not be saved.');
toast('Registration removed.', {
description: 'You can undo this change.',
action: { label: 'Undo', onClick: () => restoreRegistration() },
});Toasts use ngx-sonner with rich colors disabled. All statuses have neutral text and a white surface. Status icons are filled Phosphor icons in the 500 shade of the theme's success, info, warning and error colors. The close icon and loading spinner also use Phosphor. The default position is bottom-right, duration is 4000ms, and a close button is enabled. Configure position, duration, closeButton, expand, visibleToasts and offset on CaToaster. The toast API supports descriptions, actions, promises, updates by id and toast.dismiss(). Hovering the toasts or moving focus into one, such as with Tab to its close or action button, pauses every toast's timer; the timers resume once both the pointer and focus have left. The toaster renders in the top layer: while a CaDialog or CaDrawer is open, toasts show above it and its backdrop, in the same corner, and stay clickable.
Tables
CaTable renders your <tr>, <th> and <td> templates inside a native <table>. Bind [caTableBody]="rows" to type let-row; with a bare caTableBody the row is any. Sorting (th[caSortableColumn]), pagination (paginator, rows, rowsPerPageOptions), globalFilter, selection (selectionMode, [(selection)], tr[caSelectableRow], ca-table-checkbox), row expansion (caTableRowExpansion, button[caRowToggler], [(expandedRowKeys)] by dataKey), loading and scrollable are opt-in. The table's paginator is a ca-paginator, which also works on its own for lists that are not tables.
Tabs
Tabs on router links follow the current route, and the closest match is selected. They render in one of two ways:
- A tablist (the default) for links that switch the content of one view, like in-page tabs. Arrow keys move between tabs, Enter or Space follows a link, and
ca-tab-router-panelaround the<router-outlet>is the tab panel. - Navigation, with
navon theca-tab-list, for links that lead to separate pages, such as a section's sub-pages. The list is a<nav>named bylabel, the links have no tab roles, the current one hasaria-current="page", and Tab moves between them. Aca-tab-router-panelis optional here; it has no role but still keeps focus in place when the route replaces the focused element.
Both look the same: the selected tab and the current link have the same underline.
<ca-tabs>
<ca-tab-list nav label="Competition">
<a caTab value="overview" routerLink=".">Overview</a>
<a caTab value="results" routerLink="results">Results</a>
</ca-tab-list>
<router-outlet />
</ca-tabs>Tooltips
caTooltipDisabled turns a tooltip off, and one that is open closes. To show a tooltip only at some screen sizes, give caTooltipMedia a media query: caTooltipMedia="(min-width: 64.0625rem)" shows it from the lg breakpoint up. While the query does not match, the tooltip behaves as disabled, closes if it is open and removes its id from the host's aria-describedby. On the server the query is not evaluated.
Tags
dot adds a small leading dot in the tag's text colour, and pulse shows the dot and pulses it, for a live status: <ca-tag severity="error" pulse>Live</ca-tag>. The dot is hidden from screen readers, so the text must name the status. The pulse stops when the user prefers reduced motion.
Icons
Components take icons as Phosphor icon names from @ng-icons/phosphor-icons, for example icon="phosphorTrash". Register the icons you pass with provideIcons in your component or application providers:
import { provideIcons } from '@ng-icons/core';
import { phosphorTrash } from '@ng-icons/phosphor-icons/regular';
@Component({ providers: [provideIcons({ phosphorTrash })] })Badges and skeletons are hidden from screen readers: badge overlays describe their host through aria-describedby, and a loading region should carry aria-busy.
Overlays
CaPopover opens from any event with toggle($event) or show($event) and anchors to that element, so origin is optional; close it with hide() (close() is deprecated). A programmatic CaMenu.close() only returns focus to the trigger when focus was inside the menu. Size a drawer with --ca-drawer-width (left and right, default 22rem) or --ca-drawer-height (top and bottom). CaDialog's breakpoints input takes { '<max-width>': '<width>' }; the narrowest match wins.
Menus, selects, tooltips and other overlays render next to their trigger as inline popovers, so they keep working inside a modal dialog or drawer.
Forms
Value components (select, multiselect, autocomplete, segmented control, slider, number input, date picker and color picker) have a value model and disabled, invalid and touched inputs. They implement the signal forms control contract, so [formField] binds them directly, including disabled and invalid state.
For ngModel, formControl and formControlName, add CaFormsSupport to your imports next to FormsModule or ReactiveFormsModule:
import { FormsModule } from '@angular/forms';
import { CaFormsSupport, CaSelect } from '@northtech/climbalong-ui';
@Component({
imports: [FormsModule, CaFormsSupport, CaSelect],
template: `<ca-select label="Round" [options]="rounds" [(ngModel)]="round" required />`,
})The native form directives accept invalid too, and show the invalid state automatically for ngModel, reactive forms and signal forms once the field is touched. caTextarea autoResize grows and shrinks with its content. To bind non-boolean values to a switch, import CaSwitchValues next to CaSwitch and set trueValue and falseValue.
A control shows its invalid state when its form control is invalid and touched, or when you set invalid. The native form directives (caTextInput, caTextarea, caNumberInput, caSwitch) use Angular's built-in accessors.
Required fields and descriptions
Select, autocomplete and date picker forward required and describedBy to their focusable control: the select's combobox trigger, and the text input of the autocomplete and date picker. On the native directives, set required and aria-describedby on the element itself.
required(boolean, defaultfalse) setsaria-required="true"on the control, and removes it when it turns off. It is also on when[formField]binds a field with arequiredrule, or when a reactive orngModelcontrol hasValidators.required. On its own the input does not validate, and it never sets the nativerequiredattribute. Next tongModel,formControlorformControlName, therequiredattribute also adds Angular's required validator, as it does on a native input. An inline date picker has no element that can carryaria-required.describedBy(string | null | undefined, defaultnull) takes space-separated element ids, such as a hint and an error message. They go first in the control'saria-describedby, the calendar group's for an inline date picker. When the binding changes, the old ids are removed and ids added by other code stay.nullor an empty string removes them all.
Neither input changes the value, emits valueChange or marks the form dirty or touched. label and inputId keep naming the control. Show the error message only while it applies, and include its id only then:
<form [formGroup]="form">
@let roundError = form.controls.round.invalid && form.controls.round.touched;
<ca-select
label="Round"
[options]="rounds"
formControlName="round"
[describedBy]="roundError ? 'round-hint round-error' : 'round-hint'"
/>
<p id="round-hint">The round you judge.</p>
@if (roundError) {
<p id="round-error">Choose a round.</p>
}
</form>Here Validators.required on round sets aria-required. Without it in the control's validators, add required to the element.
Options
Select-like components accept any option objects. optionLabel, optionValue and optionDisabled take a property name or a function. Without optionValue, an option with a value property uses it and any other option is its own value; match object values with dataKey or compareWith. Set group with optionGroupLabel and optionGroupChildren for grouped options. Customize option content with <ng-template caOption let-option>. loadingMessage sets the text shown while loading, and inputId sets the trigger's id so an external <label for> can name it.
ca-segmented-control allows deselecting by default (allowEmpty), like PrimeNG's SelectButton: clicking the selected option clears the value to null. Pass [allowEmpty]="false" to always keep one option selected. ca-autocomplete never filters by itself: handle (search) and update [suggestions].
Fonts
The package bundles Inter in fonts.css. It does not bundle Graphik: Graphik is a licensed typeface, so each project installs its own copy of the font files and declares the faces itself.
The theme uses Graphik for text-display-large, text-display-medium and text-heading-large, and for the font-graphik utility. It needs these files:
| File | font-weight | font-style | Used by |
| --------------------------- | ------------- | ------------ | ------------------------------------------- |
| Graphik-bold.woff2 | 700 | normal | text-heading-large |
| Graphik-bold-italic.woff2 | 700 | italic | text-display-large, text-display-medium |
The family name must be exactly Graphik. Without the faces, those styles fall back to the system sans-serif. Add more weights only if your project uses font-graphik with them.
Put the files where your build serves or bundles them, for example src/assets/fonts/, and declare them in a stylesheet that is part of the global styles:
@font-face {
font-family: 'Graphik';
src: url('./assets/fonts/Graphik-bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Graphik';
src: url('./assets/fonts/Graphik-bold-italic.woff2') format('woff2');
font-weight: 700;
font-style: italic;
font-display: swap;
}The playground does exactly this in projects/playground/src/graphik.css.
Prompt for an agent
Copy this into your coding agent to set up Graphik in a project that uses this package:
Set up the Graphik font for @northtech/climbalong-ui in this project.
The library's theme uses font-family 'Graphik' at weight 700, in normal and italic styles, for
text-display-large, text-display-medium and text-heading-large. The package does not ship Graphik.
1. Find the licensed Graphik files in this project: Graphik-bold.woff2 and
Graphik-bold-italic.woff2. If they are not here, stop and ask me for them. Do not download
Graphik from the internet.
2. Put them in a folder the build serves or bundles, such as src/assets/fonts/.
3. Create a stylesheet with two @font-face rules: font-family 'Graphik', font-weight 700,
font-style normal and italic, font-display swap, each pointing at its file with a URL relative
to the stylesheet.
4. Add the stylesheet to the global styles, after node_modules/@northtech/climbalong-ui/fonts.css,
which provides Inter.
5. Check it: on a page that uses text-display-large, document.fonts.check('italic 700 32px Graphik')
must return true after document.fonts.ready.Theme
The theme replaces Tailwind defaults with Climbalong values:
font-sansis Inter.font-interandfont-graphikare also available.- The
neutralscale is Climbalong's.brand,error,warning,successandinfoscales are added, each 50–900. shadow-sm,shadow-mdandshadow-lguse Climbalong's shadows.- The
lgbreakpoint is 1025px (64.0625rem).xs(400px) and3xl(1600px) are added.
Motion values are available as --ca-motion-duration (200ms), --ca-motion-feedback-duration (150ms), --ca-motion-drawer-duration (250ms), and the easings --ca-motion-ease, --ca-motion-ease-in-out and --ca-motion-drawer-ease.
Organization colors
Components use the primary colors, not the brand scale. Override them to apply an organization's colors, either in CSS or at runtime on the document element:
:root {
--color-primary: #1189c6; /* brand-500: borders, focus rings, indicators, icons */
--color-primary-strong: #1189c6; /* brand-500: fills behind text, primary text on white */
--color-primary-hover: #0c6592; /* brand-600: hovered fills and primary text */
--color-primary-subtle: #d0edfb; /* brand-100: soft fills, hovered surfaces */
--color-primary-subtle-hover: #a6dcf7; /* brand-200 */
--color-primary-subtle-foreground: #084563; /* brand-700: text on soft fills */
--color-primary-foreground: #fff; /* text on strong fills */
}The same colors are available as utilities, for example bg-primary-strong and text-primary-subtle-foreground.
Contrast
Primary fills and primary text use the brand orange. With white it reaches 3.3:1, which meets WCAG AA for large text (18.66px bold or 24px) and for UI such as borders, focus rings and icons, but not the 4.5:1 AA needs for body text. Every other default pair meets 4.5:1:
| Text | Background | Ratio | Used by |
| --------------------------- | ----------------------------------------- | --------- | --------------------------------------------------------------- |
| primary-foreground | primary-strong / primary-hover | 3.3 / 4.7 | Primary button, tag and badge, selected segmented control, card |
| primary-strong | white | 3.3 | Outlined, ghost and link buttons, selected tab, focused select |
| primary-subtle-foreground | primary-subtle / primary-subtle-hover | 5.9 / 4.7 | Subtle button, secondary tag, multi-select chips |
| neutral-600 | white | 5.3 | Placeholders |
| white | error-600 / error-700 | 5.6 / 8.8 | Error button |
| white | brand-600, brand-700 | 4.7 / 7.5 | Your own fills behind body text |
| white | success-800, info-700, brand-900 | 6.4+ | Your own fills |
| neutral-900 | warning-500, success-500, primary | 5.3+ | Your own fills |
Where a product needs AA for body text on primary, set --color-primary-strong to brand-600 and --color-primary-hover to brand-700; every component follows. When you choose organization colors, keep primary at 3:1 or more against white. neutral-500 (3.7:1) is for disabled text only. White on success-500 or warning-500 is about 1.6:1; use dark text on those, or a darker step.
Typography
Each utility sets family, size, line height, weight and style. Sizes are font size / line height in pixels. Display and heading sizes step up from the lg breakpoint.
| Utility | Family | Weight | Size | From lg |
| --------------------- | ------- | ---------- | ----- | --------- |
| text-display-large | Graphik | 700 italic | 48/56 | 96/104 |
| text-display-medium | Graphik | 700 italic | 32/40 | 64/72 |
| text-heading-large | Graphik | 700 | 24/32 | 32/40 |
| text-heading-medium | Inter | 700 | 20/28 | 24/32 |
| text-heading-small | Inter | 700 | 16/24 | 20/28 |
| text-title | Inter | 600 | 16/24 | |
| text-body | Inter | 400 | 16/24 | |
| text-body-small | Inter | 400 | 14/20 | |
| text-label | Inter | 600 | 14/20 | |
| text-label-small | Inter | 600 | 12/16 | |
| text-caption | Inter | 400 | 12/16 | |
Spectator's type-* classes map directly: for example, type-heading-lg is text-heading-large, type-heading-xs is text-title and type-body-md is text-body.
Buttons
| Size | Text button | Circle | Circle icon |
| -------- | ----------- | ------ | ----------- |
| small | 32px | 36px | 16px |
| medium | 36px | 40px | 20px |
| large | 40px | 44px | 24px |
Text buttons use Inter 600 at 14/20px, 16px icons, an 8px gap and 4px corners. Outlines are drawn inside the button, so they do not change its size. Variants are primary (default), subtle, outlined, ghost, ghost-black, ghost-white, outlined-white, basic, link and error. Use shape="circle" for icon-only buttons and give them an accessible name.
loading disables the button, marks it busy and replaces its content with a spinner without changing its size. On links, disabled and loading set aria-disabled and block navigation. A natively disabled button drops keyboard focus to the page, so give a button that can turn disabled while focused (a carousel's Next button, a Save button that starts loading) disabledFocusable: it then gets aria-disabled, stays focusable and ignores clicks.
Add an icon with icon (a Phosphor icon name) and iconPos="right"; a button with only an icon becomes square and needs an aria-label. badge shows a count or, with true or '', a dot in the corner.
CaButton is a component with an attribute selector, so a template reference on the element (#save) or viewChild('save') gives the CaButton instance, not an ElementRef. It has nativeElement and focus(), so save.nativeElement.focus() and save.focus() both work; viewChild('save', { read: ElementRef }) still gives the ElementRef. Every non-circle button is at least as wide as it is tall, so icon-only buttons are square whether the icon comes from icon or a projected <ng-icon>.
To restyle a single button, set --ca-btn-bg, --ca-btn-fg and --ca-btn-border, and their --ca-btn-hover-* counterparts.
Using with AI agents
The package ships documentation for coding agents, generated for each version:
llms.txt: an index of guides and components. Start here.docs/components/*.md: one file per component, with its import, inputs with types and defaults, outputs, public methods, playground examples and notes.docs/guides/*.md: install, conventions, fonts, forms, icons, overlays, options, theme and typography.llms-full.txt: everything in one file.api.json: the API as JSON.skills/climbalong-ui/SKILL.md: an Agent Skills skill that points to the above. The format is shared by many coding agents, so any agent that supports skills loads it when a task involves the library.
In an application that uses the package, add this line to AGENTS.md, which most coding agents read. Claude Code reads CLAUDE.md instead, so add the line there too, or import the file with @AGENTS.md:
For @northtech/climbalong-ui components, read node_modules/@northtech/climbalong-ui/llms.txt before writing templates, and the component's file in docs/components/ before using it.Agents that support skills can also load the skill. Link it into the project's skills folder; because it is a link, it follows the installed version. .agents/skills/ is the shared location that agents such as GitHub Copilot, OpenCode and Antigravity read:
mkdir -p .agents/skills
ln -s ../../node_modules/@northtech/climbalong-ui/skills/climbalong-ui .agents/skills/climbalong-uiClaude Code reads .claude/skills/ instead:
mkdir -p .claude/skills
ln -s ../../node_modules/@northtech/climbalong-ui/skills/climbalong-ui .claude/skills/climbalong-uiIf your agent uses another folder, link the skill there the same way. Where symbolic links are not available, copy the folder instead, and copy it again after each upgrade of the package.
scripts/build-agent-docs.mjs generates these files into dist/climbalong-ui as the last step of npm run build. It reads the API from the published typings, input defaults from the source, descriptions, usage and examples from the playground pages, and guides from this README. A README section named like a playground page (Toasts, Tables) becomes that component's notes; the other sections become guides. To document something for agents, write a JSDoc comment on the class or input, or add a playground example.
Package and verify
For now, increment only the package's patch version: 0.0.1, 0.0.2, 0.0.3, and so on.
npm run pack # builds, checks the agent docs and packs the library tarball
npm test # builds the playground and runs the browser suite
npm run test:compat -- 21.2.23 # installs the tarball in a separate Angular app and tests it
npm run docs:check # fails on documentation gaps or docs missing from the tarball
npm run check:format
npm run release # runs the tests, checks the docs and publishes dist/climbalong-uidocs:check fails when a component class has no JSDoc comment, a library folder has no playground page, or a page has no description, usage snippet or example. Publish with npm run release. The published package is dist/climbalong-ui, which has no npm scripts of its own, so npm publish ./dist/climbalong-ui on its own skips the build and the docs check.
The library is compiled with Angular 21.2.23, CDK 21.2.14, TypeScript 5.9 and partial Ivy compilation. The workspace uses Node 22.14 through .nvmrc. The compatibility run covers the minimum supported Angular version and the latest 21.x.
