@hollaco/style-center
v2.1.0
Published
HollaCo design system — tokens, foundation and component CSS.
Maintainers
Readme
HollaCo Style Center
Central styling for the HollaCo footprint. One place that defines colour, type, spacing, layout and shared components, so every property looks like the same company built it.
Plain CSS is the source of truth — it works in the Webflow properties as-is — with a Tailwind bridge for the Next.js properties.
npm install @hollaco/style-center/* Everything the foundation provides. */
@import "@hollaco/style-center/hollaco.css";
/* Components are opt-in — import only what the page uses. */
@import "@hollaco/style-center/components/navigation.css";
@import "@hollaco/style-center/components/footer.css";For a Tailwind v4 property, add the bridge after the foundation — Tailwind first, then the foundation, then the bridge:
@import "tailwindcss";
@import "@hollaco/style-center/hollaco.css";
@import "@hollaco/style-center/tailwind-theme.css";The three token tiers
Tokens are layered, and the layer you reach for matters.
| Tier | File | What it holds | Use it when |
|---|---|---|---|
| 1 — Primitives | foundation/tokens.primitives.css | Raw values with no meaning: --hc-color-teal-900, --hc-spacing-16 | Almost never. Reference these only when defining a tier-2 token. |
| 2 — Semantic | foundation/tokens.semantic.css | Roles: --hc-color-background-default, --hc-color-content-subtle, --hc-spacing-lg | Always. This is the tier components read. |
| 3 — Responsive | foundation/tokens.responsive.css | Roles that change with viewport: --hc-typography-font-size-heading-1 | For type and vertical rhythm, which must scale. |
The rule: a component should never reference a primitive. If you find
yourself writing var(--hc-color-teal-400) in a component, the semantic tier
is missing a role worth naming — add it there instead. That indirection is the
whole point: it is what lets the brand colour change in one line, and what lets
dark mode work without a single component knowing it exists.
Brand colour and contrast
Three brand tokens, because one value cannot serve both fills and text.
| Token | Primitive | On white | Use for |
|---|---|---|---|
| --hc-color-brand | teal-900 #188888 | 4.27:1 | Decorative surfaces that carry no text — rules, bars, a section ground. Borders. Display type at 24px+, or bold 18.66px+. |
| --hc-color-brand-deep | teal-1000 #136a6a | 6.37:1 | Brand-coloured text on a light ground. Passes WCAG AA for body copy. |
| --hc-color-brand-bright | teal-500 #4ec3c3 | — | Accents on dark grounds; section-heading bars, highlights. |
The large-text threshold is in points, not pixels. WCAG relaxes to 3:1 only at 18pt (24px), or 14pt bold (18.66px). An earlier version of this table said "bold 14px+", which is not the same thing and is not large text — and that is precisely how
.con-button.brandshipped a 14px bold label at 4.27:1 through review. If a fill carries text below those sizes, it owes the full 4.5:1.
You rarely pick between them by hand. --hc-color-content-brand — the token you
reach for when text should be brand-coloured — already resolves to
--hc-color-brand-deep in the light theme and --hc-color-brand-bright under
.dark. Reach for the role, and the contrast is handled.
--hc-color-border-brand resolves to --hc-color-brand, where 4.27:1 is the
right answer because nothing reads text off a border.
Brand fills come in two roles, because a fill that carries text owes more contrast than one that does not:
| Role | Resolves to | Use for |
|---|---|---|
| --hc-color-background-brand | --hc-color-brand | Decorative fills — rules, bars, a section ground. Nothing reads text off it. |
| --hc-color-background-brand-strong | --hc-color-brand-deep (light) · --hc-color-brand-bright (dark) | Any fill carrying text or an icon. Pairs with --hc-color-content-on-brand-strong. |
Read the -strong pair together — background-brand-strong under
content-on-brand-strong — and both themes clear WCAG AA without anyone
choosing a value by hand. -hover and -active variants exist for the same
reason, and they are roles rather than literals because the direction inverts by
theme: darker on hover against a light ground, brighter against a dark one.
tests/contrast.test.mjs resolves these chains and fails the build if any state
drops below 4.5:1.
Naming
Everything is prefixed --hc-, with no exceptions, so a consuming app can
never collide with its own --grid-gutter or --spacing-lg.
--hc-color-<role>-<variant> --hc-color-content-subtle
--hc-spacing-<step> --hc-spacing-lg
--hc-typography-<prop>-<role> --hc-typography-font-size-heading-2
--hc-nav-* --hc-footer-* component-scopedComponent classes use the .hc- prefix (.hc-navLink, .hc-menu-headline).
Layout and utility classes are unprefixed where they read as plain English
(.container, .four-up, .heading-2, .spacing-lg, .con-button).
Forms
components/form.css is the one component with a markup contract worth stating
here, because the error state depends on getting it right:
<div class="hc-field">
<label class="hc-field__label" for="email">Email address</label>
<input class="hc-field__input" id="email" type="email"
aria-invalid="true" aria-describedby="email-error">
<p class="hc-field__error" id="email-error">Enter a valid email address.</p>
</div>The error styling keys off aria-invalid, not a class. Setting the attribute is
what a screen reader needs anyway, so there is no way to turn the field red
without also announcing it — and no way to announce it without turning it red.
.hc-field__error:empty hides itself, so a container the script fills in later
leaves no stray mark on an untouched form. Use .hc-field__select for a native
<select>, and .hc-field__help for non-error hint text.
It covers what every HollaCo form shares. Property-specific arrangements — a verification-code box row, a password-strength list — belong to the property until a second one needs them.
Dark mode
Add .dark to any element. A whole page, or one section:
<section class="dark">…</section>The semantic tier re-points and everything inside follows. No component restates a colour for dark mode, and none should need to.
Layout
hollaco.css entry point — imports the whole foundation
foundation/
tokens.primitives.css tier 1
tokens.semantic.css tier 2 (+ the .dark overrides)
tokens.responsive.css tier 3
reset.css element defaults
typography.css the type scale
spacing.css .spacing-* section rhythm
layout.css .container and the column grid
buttons.css .con-button and its variants
parallax.css scroll-driven motion — opt-in, not in hollaco.css
components/ opt-in, one import each
utilities.css .visually-hidden and friends
tailwind-theme.css @theme inline bridge for the Next.js properties
assets/ inline SVGs the CSS referencesEvery path above is in the published package. The repository also carries a
preview/ page, build tooling and reference material; none of it is published.
Adopting it
Adopt in this order, and stop wherever it is enough:
- Import the foundation. Tokens become available. Nothing changes visually.
- Alias your existing variables to the
--hc-*roles. One line each; your component code keeps working untouched. - Replace the aliases at your own pace. When the last consumer of an old name is gone, delete the alias.
Step 2 is what makes this safe — a property can sit there indefinitely and still get the benefit of one place to change the brand colour, with no big-bang rewrite:
:root {
--accent: var(--hc-color-brand);
--background: var(--hc-color-background-default);
--foreground: var(--hc-color-content-default);
}HollaCo properties: the per-property migration path, including the full variable
mapping tables, is in ADOPTION.md in the repository.
Working on the style center itself
Open preview/index.html from the repository root after any change. It renders
every token, light and dark, at desktop and mobile widths, so a collapsed font
weight, a transparent fill or a size that stopped scaling is visible rather than
silent.
That page is a repository file and is not part of the published package.
