@fungalcode/ui
v0.1.0
Published
Shared Angular UI components and design tokens, themed entirely through CSS custom properties.
Downloads
164
Readme
@fungalcode/ui
Angular components and design tokens, shared across projects. Plain CSS, themed entirely through custom properties — no Tailwind, no theming API, no build step beyond the one Angular already runs.
Requires Angular 21. @angular/router is an optional peer, needed only by the
components that generate their own links.
Install
npm install @fungalcode/uiThen, at the top of your src/styles.css:
@import url('@fungalcode/ui/styles/tokens.css');That is the only required step. Component styles ship inside the components.
Theme it
The shipped palette is a placeholder — a restrained slate, deliberately
nobody's brand. Override any --ui-* token from your own :root. Your rules
are unlayered and the library's live in @layer ui.tokens, so yours win
whatever order the stylesheets load in.
:root {
--ui-color-brand: #7a235a;
--ui-font-family: 'Plus Jakarta Sans', sans-serif;
--ui-radius-md: 15px;
--ui-space-unit: 0.3125rem; /* the whole spacing scale, from one knob */
}Two rules worth knowing:
--ui-color-on-brand*means "ink that sits on the brand colour". If you set a light brand, these must go dark. Getting it backwards is the most common theming mistake and it fails contrast silently.- Component knobs (
--ui-button-padding-inline,--ui-hero-scrim, …) are never declared, only read with a fallback. So you can set one on:root, on a section, or on a single instance'sstyleattribute.
Optional stylesheets
@import url('@fungalcode/ui/styles/base.css'); /* reset + base typography */
@import url('@fungalcode/ui/styles/utilities.css'); /* .ui-container, .ui-visually-hidden */Components never need either. base.css gives a page the same baseline —
skip it if your project already has a reset it likes.
Components
| | |
|---|---|
| UiButton | a[uiButton], button[uiButton] — attaches to a native element |
| UiHero | h1, lead, actions, three media placements |
| UiQuote | pull quote, projected body |
| UiLinkCards | grid of cards that link somewhere |
| UiStepCards | numbered sequence |
| UiInfoCards | cards that explain something |
| UiTestimonials | feedback, with optional ratings and portraits |
| UiFaq + UiFaqItem | native <details> accordion |
| UiContactForm | fields, validation and ARIA — you own the transport |
import { UiButton, UiFaq, UiFaqItem } from '@fungalcode/ui';
@Component({ imports: [UiButton, UiFaq, UiFaqItem] })The button is an attribute
<a uiButton routerLink="/contact">Get in touch</a>
<button uiButton type="submit" [disabled]="sending()">Send</button>It attaches to an element you write, so href, routerLink, type and
disabled are the real attributes and behave as such. Content is projected,
so an icon can sit inside the label.
The contact form does not send
It owns the fields, the validation, the error wiring and the honeypot. You own
the transport, and report back through status:
<ui-contact-form
[labels]="german"
[status]="status()"
privacyHref="/privacy"
(submitted)="send($event)"
/>The emitted message carries elapsedMs — how long the form took to fill in —
for your server to weigh. A submission that tripped the honeypot is silently
accepted and never emitted.
All copy comes from labels; the library ships English defaults and no
translations.
Development
npm run storybook # the showcase, and the development loop
npm test # unit tests, jsdom
npm run test:stories # every story in Chromium, with axe
npm run build:lib
npm run pack:local # a tarball, exactly as the registry would ship itTwo rules that are easy to trip over:
ng test, nevernpx vitestfor the unit tests — the Angular builder installs the Vitest globals, so a direct run fails withReferenceError: describe is not defined.- Stories import from
@storybook/angular-vite, not@storybook/angular, which is not installed. The error names your story file, not the package.
Every value a component reads must be a --ui-* token. The check is
mechanical, and it is the difference between a library and one project's CSS:
grep -rn 'var(--' projects/ui/src/lib | grep -v 'var(--ui-' # must be empty