npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tx-angular-design-system/ui

v1.0.3

Published

TraXtion design system and Angular UI component library — design tokens, global theme and standalone components for TraXtion applications.

Readme

@tx-angular-design-system/ui

The TraXtion design system and Angular UI component library — design tokens, a global theme, and standalone components for TraXtion applications.

Built for dense operational software: failure reporting, dispositions, part catalogues. It assumes screens full of records rather than pages of prose.


Install

ng add @tx-angular-design-system/ui

That single command configures the application. It:

  1. registers the global theme, which applies the type ramp, spacing scale and colour tokens to bare HTML — headings, body text, tables and form controls are styled before you use a single component;
  2. loads the type stack (Archivo, IBM Plex Sans, IBM Plex Mono) by adding the font links to index.html;
  3. points Sass at node_modules, so component stylesheets can reach the token API.

Every step is idempotent — re-running it after an upgrade changes nothing that is already correct.

npm install @tx-angular-design-system/ui

Import the theme at the top of src/styles.scss:

@use '@tx-angular-design-system/ui/styles/theme';

Not using Sass? Add the precompiled stylesheet to angular.json instead:

"styles": ["node_modules/@tx-angular-design-system/ui/styles/theme.css", "src/styles.scss"]

Then add the fonts to index.html:

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
  href="https://fonts.googleapis.com/css2?family=Archivo:wght@500;600;700&family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@400;500;600&display=swap"
  rel="stylesheet"
/>

Use

Every component is standalone. Import the ones a template actually uses:

import { Component, signal } from '@angular/core';
import { TxButton, TxPanel, TxBadge, TxIcon } from '@tx-angular-design-system/ui';

@Component({
  selector: 'app-reports',
  standalone: true,
  imports: [TxPanel, TxButton, TxBadge, TxIcon],
  template: `
    <tx-panel heading="Failure reports">
      <button txPanelActions txButton variant="outline" size="sm">
        <tx-icon name="download" /> Export
      </button>

      <tx-badge status="warn">Awaiting disposition</tx-badge>
    </tx-panel>
  `,
})
export class Reports {}

There is no NgModule, and no barrel that drags the whole library into your bundle.


Design tokens

Everything is published as a --tx-* custom property, in two layers.

Primitives are the raw palette and scales — --tx-color-brand, --tx-space-4, --tx-text-base.

Semantics are what components actually read — --tx-text-color, --tx-bg, --tx-border, --tx-focus-ring-color. Retheming means overriding this layer, not the palette.

// Anywhere after the theme import
:root {
  --tx-color-brand: #0a7cff; // rebrand
  --tx-radius-md: 4px; // sharper corners
  --tx-body-size: 14px; // denser type
}

Colour

| Group | Tokens | | ------- | ------------------------------------------------------------------ | | Brand | --tx-color-brand, -deep, -dark, -tint, -edge | | Ink | --tx-color-ink, -navy, -navy-2, -muted, -subtle, -faint | | Surface | --tx-color-surface, -canvas, -sunken, -line, -line-2 | | Status | --tx-color-{success,danger,warn,info,neutral} + -tint, -edge, -ink |

--tx-color-brand is the identity green. Use --tx-color-brand-deep for text on light surfaces — the base green does not meet contrast on white.

Typography

Archivo for display, IBM Plex Sans for body, IBM Plex Mono for data and labels.

| Token | Size | Used for | | ----------------- | ---- | --------------------------------- | | --tx-text-3xs | 10px | mono micro-labels | | --tx-text-xs | 12px | captions, hints | | --tx-text-sm | 13px | dense table text, small buttons | | --tx-text-md | 14px | controls, table body | | --tx-text-base | 15px | body copy | | --tx-text-lg | 17px | wordmark, section lead | | --tx-text-xl | 20px | sub-headings | | --tx-text-2xl | 25px | page titles | | --tx-text-3xl | 32px | stat values | | --tx-text-4xl | 40px | display |

The mono micro-label — tiny, uppercase, widely tracked — marks every piece of metadata in the system. It is what makes data read as an instrument panel rather than as prose.

Spacing

A 4px grid: the token number times four is the pixel value.

--tx-space-0 · -05 (2px) · -1 (4px) · -15 (6px) · -2 (8px) · -25 (10px) · -3 (12px) · -35 (14px) · -4 (16px) · -5 (20px) · -6 (24px) · -7 (28px) · -8 (32px) · -10 (40px) · -12 (48px) · -16 (64px)

The half-steps exist because this UI is dense enough to need the odd 2, 6, 10 and 14px.

The Sass API

For media queries and the mixin library:

@use '@tx-angular-design-system/ui/styles' as tx;

.my-widget {
  padding: var(--tx-space-4);

  @include tx.panel;
  @include tx.below('md') {
    padding: var(--tx-space-3);
  }
}

| Mixin | Purpose | | -------------------- | ------------------------------------------------ | | below($bp) / from($bp) | Media queries — xs sm md lg xl | | label | The mono micro-label treatment | | heading($size) | Display face, tightened tracking, navy | | panel | The standard bordered, raised surface | | focus-ring | Keyboard-only focus outline | | truncate, line-clamp, visually-hidden, scroll-x | Layout helpers | | groove | The tread-groove brand ornament | | transition(...) | Tokenised transition, reduced-motion aware |

Use the custom properties for values and the mixins for patterns. The Sass functions (space(), radius(), breakpoint()) exist for the places CSS variables cannot go — chiefly media queries.


Components

Layout

| Component | Notes | | --- | --- | | tx-shell | Application frame. | | tx-topbar | Brand mark, app name, account controls. | | tx-sidebar | Collapsible navigation rail. Icon-only when collapsed; a drawer with a backdrop below md, opened by [txSidebarToggle]. | | tx-nav-group, [txNavItem] | Groups and items inside the sidebar. | | [txShellContent] | The main content region. | | tx-page-header | Page title, one line of context, page-level actions. | | tx-panel, tx-panel-header, tx-panel-footer | The primary content surface. | | tx-tabs, tx-tab | Full ARIA tabs pattern with arrow-key navigation. |

Actions

button[txButton], a[txButton] — variants primary, accent, outline, ghost, danger; sizes sm, md, lg; plus loading, selected, iconOnly, fullWidth.

A loading button keeps its label's width and stays focusable. Disabling a button mid-interaction drops it from the tab order and moves the user's focus, so loading sets aria-disabled and swallows clicks instead.

Data display

tx-badge · tx-chip · tx-avatar · tx-tile · tx-meter · tx-key-value / tx-key-value-row · tx-spinner · tx-empty-state

A chip is normally a token the user can dismiss. Add clickable and it becomes a real <button> — focusable, keyboard-operable, and reporting selected as aria-pressed — which is what a row of chips needs to work as a filter bar:

<tx-chip
  clickable
  [removable]="false"
  [selected]="filter() === status"
  (activated)="filter.set(status)"
>
  {{ status }}
</tx-chip>

Do not bind (click) on the host instead: <tx-chip> is an element, not a control, so it takes no focus, answers no keyboard and is announced as nothing.

Reorder list

A list whose rows the user can reorder by dragging — or entirely by keyboard.

<tx-reorder-list
  [(items)]="stages"
  [itemLabel]="stageLabel"
  [trackBy]="stageId"
  ariaLabel="Approval stages"
  (reordered)="save($event)"
>
  <ng-template [txReorderItem]="stages()" let-stage let-index="index">
    <span class="tx-label">{{ index + 1 }}</span>
    <span>{{ stage.name }}</span>
  </ng-template>
</tx-reorder-list>

Dragging alone is not enough: a drag-only list is unusable by keyboard, and awkward with a screen reader or a tremor. Every row's handle is a real button — Space picks the row up, arrows move it, Home/End send it to the ends, Escape cancels and restores the order it started in, and every position change is announced through a live region.

The held row follows the pointer exactly, and the rows it passes slide one slot out of its way. The array itself is left alone until the row is dropped — reordering it on every pointer move makes the held row jump between slots instead of tracking the finger.

items and reordered therefore settle at the same moment. Persist from reordered: it fires only when the user changed the order, whereas items also changes when you load data into the list.

Bind the same array to [txReorderItem] as to items. The value is never read; it exists so TypeScript can infer the row type and check let-stage for you.

Tree

A hierarchy of nodes — the parts breakdown of a product variant, nested to any depth.

<tx-tree [nodes]="parts()" [(selected)]="selectedPartId" ariaLabel="Parts breakdown">
  <ng-template txTreeActions let-node>
    <button txButton size="sm" variant="outline" (click)="rename(node)">Rename</button>
    <button txButton size="sm" variant="outline" (click)="move(node)">Move</button>
  </ng-template>
</tx-tree>
readonly parts = signal<TxTreeNode[]>([
  {
    id: 'control-box',
    label: 'Control Box',
    badge: 'SUB',
    badgeStatus: 'info',
    meta: '2S · 2M',
    children: [
      { id: 'chassis', label: 'Chassis', badge: 'CMP', badgeStatus: 'success', meta: '2S · 2M' },
    ],
  },
]);

The node shape is deliberately generic: the library knows about labels, badges and a line of metadata, not about parts, symptoms or failure modes. Format your domain data into badge and meta before handing it over.

Nodes are expanded by default; collapsible adds a disclosure control to every node that has children. Expansion is stored as the exceptions — the ids in collapsed — so a tree loaded from the server is fully open without the host having to walk it and enumerate every branch.

Row actions stay hidden until a row is hovered, focused or selected. A tree of fifty parts with three buttons on every row is a wall of controls, not a hierarchy. (On touch, where there is no hover, they are always visible.)

Implements the ARIA tree pattern: one roving tab stop, so Tab moves past the whole tree rather than through every node in it. Arrows move, Right opens a closed branch then steps into it, Left closes one or jumps to the parent, Home/End reach the ends, and typing a letter jumps to the next matching label.

Tables are directives on native elements — table[txTable], th[txSortable], [txAlign] — so any table library, virtual scroller or sorting strategy you add later still works.

<div class="tx-table-scroll">
  <table txTable hoverable>
    <thead>
      <tr>
        <th txSortable="raised" [sort]="sort()" (sorted)="onSort($event)">Raised</th>
        <th txAlign="end">Parts</th>
      </tr>
    </thead>
    <tbody>…</tbody>
  </table>
</div>

Sorting cycles ascending → descending → unsorted. The third state matters: without it, a user who sorted by accident can never get back to the order the data arrived in.

Pagination

tx-paginator adds a range readout, a page-size chooser, numbered pages and step buttons. Like the directives it owns no data — it reports the page to show and leaves the slicing, or the request to the server, to you.

<tx-paginator
  [length]="sorted().length"
  [(pageIndex)]="pageIndex"
  [(pageSize)]="pageSize"
  [pageSizeOptions]="[10, 25, 50]"
  itemLabel="reports"
  showFirstLast
/>
// In memory. Sort first, then page: the other order pages a moving target.
readonly visible = computed(() =>
  txPaginate(this.sorted(), this.pageIndex(), this.pageSize()),
);

// Or against a server.
onPage(event: TxPageEvent) {
  this.load(event.pageIndex, event.pageSize);
}

Changing the page size keeps the first row you were looking at on screen rather than resetting to page one, so widening a page from 10 rows to 50 does not lose your place. If the collection shrinks under the current page, the paginator moves to the new last page and emits — otherwise the user is left on an empty table with every button disabled and nothing to click.

Row actions

tx-row-actions makes the actions column configuration rather than markup. Every field takes either a value or a function of the row, so one definition covers the whole column and each row resolves it for itself:

readonly rowActions: readonly TxRowAction<Project>[] = [
  { id: 'view', label: 'View', icon: 'view' },
  {
    id: 'archive',
    label: (project) => (project.archived ? 'Restore' : 'Archive'),
    icon: (project) => (project.archived ? 'refresh' : 'clipboard-check'),
  },
  {
    id: 'delete',
    label: 'Delete',
    icon: 'trash',
    variant: 'danger',
    disabled: (project) => project.tasks > 0,
    disabledReason: 'Close the open tasks first',
  },
];
<td class="tx-table__actions">
  <tx-row-actions iconOnly [actions]="rowActions" [row]="project" (actioned)="run($event)" />
</td>

Hide an action the user is not allowed to perform — a column of buttons that always refuse teaches people to stop reading it. Disable one they could perform if the row were in a different state, and give a disabledReason, so the greyed button explains itself rather than looking like a bug.

revealOnHover keeps the buttons hidden until the row is hovered or holds focus, which quietens a long table at the cost of discoverability. They stay visible on touch, where there is no hover to reveal them with.

Forms

tx-form-field wraps a control with its label, hint and validation message and wires the accessibility relationships — id, aria-describedby, aria-invalid — so you never set them by hand.

<tx-form-field label="Report title" required [error]="titleError()">
  <input txInput [(ngModel)]="title" />
</tx-form-field>

Controls stay native: input[txInput], textarea[txInput], select[txSelect]. Validation, autofill, formControlName and browser behaviour are untouched.

Datepicker

<tx-form-field label="Due date" hint="Type 2026-08-21, or use the calendar">
  <tx-datepicker [(value)]="dueDate" [min]="today" clearable />
</tx-form-field>

The field is a real text input with a calendar attached, not a button that opens one — somebody entering a date they already know types it far faster than they can navigate to it. YYYY-MM-DD is always accepted; pass parse to accept more. Free-text date parsing is guesswork (03/04/2026 is two different days depending on which side of the Atlantic you are on), so anything looser is the application's decision.

The value is a Date at local midnight. Time of day is discarded deliberately: a date picked from a calendar is a calendar day, and keeping the hours around invites comparisons that break twice a year when the clocks change. The writeValue path also accepts an ISO string, which is what a JSON API returns.

The calendar is a grid, as the ARIA date-picker pattern requires: arrows move by a day, PageUp/PageDown by a month (hold Shift for a year), Home/End reach the ends of the week, and the focused day is the grid's only tab stop. min and max disable out-of-range days rather than hiding them, so the shape of the month survives.

Month names, weekday names and the display format all come from Intl; set locale and weekStart (0 Sunday, 1 Monday — Monday by default, as ISO 8601 has it).

tx-checkbox and tx-radio-group / tx-radio implement ControlValueAccessor, so the same component works with two-way binding and with reactive forms.

Hint and error share one slot: an error replaces the hint, because two competing lines of guidance under one input is one line too many.

Select

Two components, because they solve different problems.

<tx-select> renders its own dropdown panel, so it looks identical in every browser — including versions of Chrome far older than the customizable-select API — and it supports real multiple selection:

<tx-form-field label="Disposition">
  <tx-select [(value)]="disposition" placeholder="Choose…" clearable>
    <tx-option-group label="In house">
      <tx-option value="repair">Repair</tx-option>
      <tx-option value="scrap">Scrap</tx-option>
    </tx-option-group>
    <tx-option value="return">Return to supplier</tx-option>
  </tx-select>
</tx-form-field>

<!-- Multiple selection: the value is an array -->
<tx-select multiple clearable [(value)]="symptoms" placeholder="Select symptoms">
  <tx-option value="drift">Sensor drift</tx-option>
</tx-select>

It implements the ARIA combobox pattern with a listbox popup. Focus stays on the trigger and aria-activedescendant tracks the highlight, so nothing has to juggle focus. Arrows move, Home/End jump to the ends, Enter/Space select, Escape closes, and typing letters jumps to the matching option. In multiple mode the panel stays open as you pick — that is the whole use case.

The panel is relocated to <body> and positioned in script, so no ancestor's overflow: hidden or transform can clip it. It opens downward and flips above only when it genuinely will not fit.

Pass compareWith when the option values are objects; the default identity check will never match two separately-deserialised copies of the same record.

select[txSelect] styles a native <select>. Still the better choice for a short, single-value list: it costs no JavaScript and gets the operating system's picker, which is what mobile users expect. Its trigger is fully styled everywhere, but the dropdown is drawn by the browser, so it can only be themed in Chrome and Edge 135+ where the customizable-select API exists. The library ships that enhancement inside @supports; everywhere else the native picker appears.

The size input on txInput and txSelect is txSize, not size — <input size> and <select size> are real HTML attributes, and shadowing them would take that behaviour away from you. <tx-select> is a custom element, so it uses plain size.

tx-checkbox and tx-radio-group / tx-radio implement ControlValueAccessor, so the same component works with two-way binding and with reactive forms.

Hint and error share one slot: an error replaces the hint, because two competing lines of guidance under one input is one line too many.

The select dropdown

A <select> is two things: a trigger, and a dropdown the browser owns. The trigger is fully styled everywhere — brand chevron, hover and focus states, and a placeholder treatment when the empty option is selected, so Choose… reads like the placeholder in the field beside it.

The dropdown is styled where the browser allows it, via the customizable select API (appearance: base-select). Where that is supported the picker becomes a real design-system surface: panel background, radius and shadow, brand-tinted selection with a check, styled optgroup headings, and a fade-in on open.

This needs Chrome or Edge 135+ (April 2025). Below that version — and in Firefox and Safari, which do not implement it at all — the browser draws its own dropdown and there is nothing CSS can do about it. That is not a misconfiguration; it is the feature's support baseline.

Seeing the system dropdown in Chrome but the styled one in Edge? The two update independently, so Edge is on a newer Chromium. Check chrome://version — anything below 135 gets the native picker.

It is written as a progressive enhancement inside @supports, not a replacement: nothing breaks in a browser that has never heard of it, and because no JavaScript combobox is involved, keyboard behaviour, type-ahead, form autofill and mobile native pickers all stay intact.

If you need the dropdown to look identical in every browser today, this approach cannot give you that — it would take a custom listbox component, trading away the native behaviour above.

The picker is pinned to open downward. Chromium's default is position-try-order: most-block-size, which opens the menu into whichever side has more room and so flips it above the field on a scrolled page even when it would fit below; the library resets that to normal and flips only on genuine overflow.

Feedback

tx-banner — something that matters while you work. danger and warn announce assertively; info and success announce politely.

TxToastService — confirmation that something just happened. The outlet attaches itself to <body> on first use; there is nothing to add to your root template.

private readonly toast = inject(TxToastService);

this.toast.success('Report submitted');
this.toast.danger('Could not reach the disposition service');
this.toast.show('Part removed', {
  action: { label: 'Undo', handler: () => this.restore(part) },
});

Errors stay on screen longer than confirmations — an error the user has not finished reading should not evaporate.

tx-dialog — something that must be decided now. Render it conditionally; it is open for exactly as long as it exists.

@if (confirmingDelete()) {
  <tx-dialog heading="Delete this report?" (closed)="confirmingDelete.set(false)">
    <p>This cannot be undone.</p>
    <ng-container txDialogFooter>
      <button txButton variant="outline" (click)="confirmingDelete.set(false)">Cancel</button>
      <button txButton variant="danger" (click)="delete()">Delete report</button>
    </ng-container>
  </tx-dialog>
}

It handles the whole modal contract: focus moves in on open and returns to the element that opened it, Tab is trapped, Escape and backdrop clicks close it, the page behind is locked from scrolling, and it relocates to <body> so no ancestor's overflow or transform can clip it.

Icons

Stroke-only glyphs on a 24px grid that inherit currentColor.

<tx-icon name="edit" />
<tx-icon name="alert" size="sm" />
<tx-icon name="reports" label="Failure reports" />

Icons are decorative by default. Pass label only for a standalone icon that carries meaning on its own.

Icons are named for what they depict rather than for the screen they first appeared on — sliders, not dispositions. A glyph named after one application's workflow is only reusable inside that application.

Add your own, or override a shipped glyph by re-using its name:

provideTxIcons({
  tyre: '<circle cx="12" cy="12" r="8.5"/><circle cx="12" cy="12" r="3"/>',
});

The value is trusted verbatim, so author it yourself — never pass user input.


Utilities

A small, deliberate layer — not a utility-first framework.

Layout tx-stack · tx-cluster · tx-row · tx-spacer · tx-cols (tx-cols-2, tx-cols-3, tx-cols-auto) · tx-master-detail · tx-tiles · tx-form-grid · tx-table-scroll

Type tx-mono · tx-label · tx-muted · tx-subtle · tx-truncate · tx-measure · tx-text-* · tx-font-*

Other tx-groove · tx-sunken · tx-visually-hidden · tx-skip-link · tx-gap-* · tx-mt-* / tx-mb-* / tx-p-*


Accessibility

Not a later pass — the components will not let you skip it.

  • Focus is keyboard-only (:focus-visible) and never suppressed without a replacement.
  • tx-dialog implements the full modal contract, including focus restoration.
  • tx-tabs implements the ARIA tabs pattern; only the active tab is in the tab order, so Tab moves into the panel.
  • tx-form-field wires label and description relationships automatically.
  • Status is never carried by colour alone — badges and banners pair colour with an icon or text.
  • prefers-reduced-motion is honoured globally, and every transition routes through one mixin.

Two things the library cannot do for you: an icon-only button still needs an aria-label, and a routed nav item needs ariaCurrentWhenActive="page" alongside routerLinkActive — a class alone tells assistive technology nothing.


Theming

Override the semantic layer after importing the theme:

@use '@tx-angular-design-system/ui/styles/theme';

:root {
  --tx-color-brand: #0a7cff;
  --tx-color-brand-deep: #0060cc;
  --tx-color-brand-tint: #e8f2ff;
  --tx-color-brand-edge: #b8d8ff;
}

Scoped themes work the same way — set the properties on any ancestor and everything inside inherits them.

Self-hosting the fonts

For air-gapped or privacy-sensitive deployments, drop the woff2 files in your assets and repoint three tokens:

:root {
  --tx-font-display: 'Archivo', system-ui, sans-serif;
  --tx-font-sans: 'IBM Plex Sans', system-ui, sans-serif;
  --tx-font-mono: 'IBM Plex Mono', ui-monospace, monospace;
}

Nothing else in the library needs to change.


Browser support

Evergreen Chrome, Edge, Firefox and Safari. The library uses CSS custom properties, :focus-visible, aspect-ratio, :has() and CSS grid without fallbacks.

One feature is deliberately a progressive enhancement rather than a baseline: the styled dropdown on the native select[txSelect] needs the customizable select API (Chrome and Edge 135+). If you need a dropdown that looks the same everywhere, use <tx-select> — see Select.

Requirements

Angular 20 or later. @angular/forms is a peer dependency, required by the checkbox and radio components.