@northwestu/nuxt-nu-design-kit
| Install | |
|---|---|
npm install @northwestu/nuxt-nu-design-kit |
|
| Latest Version: | 0.1.0 |
| Vue: | 3 |
| License: | Apache-2.0 |
| Last Updated: | Sep 17, 2026 |
| Links: | npm |
@northwestu/nuxt-nu-design-kit
Northwest University marketing design system as a Nuxt 4 layer.
Packages the official stylesheet from northwestu.edu (assets/stylesheets/style-2026.css) with thin, auto-imported Vue wrappers (NButton, NCard, NSection, …) for ergonomic use in Nuxt apps. Component demos in the playground mirror the design-system examples.
Features
- Design tokens — brand colors, space, type, radii, shadows, breakpoints (
--nu-*CSS custom properties) - Official 2026 CSS — linked externally from
www.northwestu.edu/assets/stylesheets/style-2026.cssat runtime, so consumers always get the current upstream styles; a pinned vendored copy (nu-design-system.css) ships for offline/self-hosted use - Compatibility layer —
nu-compat.cssrestores legacy classes the 2026 sheet no longer ships (stack/layout primitives,section-*tones,prose, card sub-parts, …) by aliasing them onto the 2026--nu-*tokens - 40 Vue wrappers — auto-imported
N*components over the design-system classes - Interactions — fade-in on scroll (plugin + composable), reel/carousel controls
- Typekit fonts — Proxima Nova + Zenon via the NU kit stylesheet (optional)
- Tested — Vitest unit + a11y contracts, Playwright e2e + axe scans
Requirements
- Nuxt
^3.13.0 || ^4.0.0 - Vue
^3.5.0
Installation
The package is published to npm as @northwestu/nuxt-nu-design-kit. No authentication needed.
1. Install the layer
npm install @northwestu/nuxt-nu-design-kit
# or
pnpm add @northwestu/nuxt-nu-design-kit
2. Extend Nuxt
// nuxt.config.ts
export default defineNuxtConfig({
extends: ['@northwestu/nuxt-nu-design-kit'],
})
Extending the layer gives you:
- The full design-system stylesheet (external 2026 sheet via runtime
<link>+ bundledapp/assets/css/main.css→nu-compat.css) - All
N*components, auto-imported useNFadeIn/useNReelcomposables and thenClassesutil, auto-imported- A client plugin that activates
.fade-in/.fade-in-childrenscroll reveals on every page and after each route change html[lang="en"]and runtime-controlled<head>links for the official stylesheet and Adobe Fonts (Typekit) — toggleable via runtimeConfig
Configuration
The layer's head resources are injected at runtime by a plugin, so consumers can toggle them per environment — in nuxt.config or via NUXT_PUBLIC_* env vars at deploy time.
Stylesheet
By default the official 2026 stylesheet is linked externally (https://www.northwestu.edu/assets/stylesheets/style-2026.css). To self-host instead (CSP, offline builds):
// nuxt.config.ts (consumer)
export default defineNuxtConfig({
extends: ['@northwestu/nuxt-nu-design-kit'],
runtimeConfig: {
public: {
nDesignKit: { stylesheet: 'none' },
},
},
})
Then load the pinned vendored copy yourself:
css: ['@northwestu/nuxt-nu-design-kit/css/nu-design-system.css'],
Or set NUXT_PUBLIC_N_DESIGN_KIT_STYLESHEET=none at runtime.
Typekit / fonts
The layer injects the NU Typekit stylesheet (https://use.typekit.net/lqz8krm.css) into <head>. If you self-host fonts:
runtimeConfig: {
public: {
nDesignKit: { typekit: false },
},
},
// or NUXT_PUBLIC_N_DESIGN_KIT_TYPEKIT=false
App config
The layer exposes typed app config for debugging/introspection:
// app.config.ts (consumer)
export default defineAppConfig({
nDesignKit: {
name: '@northwestu/nuxt-nu-design-kit',
version: '0.1.0',
},
})
Usage
<template>
<NSection tone="midnight">
<NContainer>
<h1>Welcome to Northwest University</h1>
<NHStack>
<NButton variant="primary" to="/apply">Apply</NButton>
<NButton variant="secondary" href="/visit">Visit</NButton>
</NHStack>
</NContainer>
</NSection>
</template>
CSS-only
You can also use design-system class names directly:
<a class="button primary" href="https://github.com/Northwest-University/mktg-npm-design-kit/blob/HEAD/apply">Apply</a>
<div class="card">…</div>
Or import the stylesheet without extending the full layer:
// nuxt.config.ts
export default defineNuxtConfig({
css: ['@northwestu/nuxt-nu-design-kit/css'],
})
Package exports:
| Export | Resolves to |
|---|---|
@northwestu/nuxt-nu-design-kit |
./nuxt.config.ts (layer entry) |
@northwestu/nuxt-nu-design-kit/css |
./app/assets/css/main.css |
@northwestu/nuxt-nu-design-kit/css/* |
./app/assets/css/* |
Design tokens
Tokens are CSS custom properties on :root:
.hero {
background: var(--nu-navy);
color: var(--nu-color-white);
padding: var(--nu-space-8);
border-radius: var(--nu-border-radius-6);
}
| Token | Value | Role |
|---|---|---|
--nu-blue |
#0068bb |
Primary brand |
--nu-navy |
#034C87 |
Deep brand |
--nu-alt |
#148dcd |
Alt blue |
--nu-amber |
#ffbc2d |
Secondary CTA |
--nu-evergreen |
#04505c |
Supporting green |
--nu-powder |
#eaf4fb |
Soft surface |
See the token block at the top of app/assets/css/nu-design-system.css for the full list (spacing scale --nu-space-*, radii --nu-border-radius-*, size scale --nu-size-*, typography, breakpoints).
The stylesheet also ships utility classes used across the demos, e.g. spacing utilities (mb-8, p-10) and the .inset full-cover link pattern; sizing (size-1…size-10) and aspect-ratio-* utilities are provided by the compat layer.
Compatibility layer
app/assets/css/nu-compat.css (imported after the official sheet) restores the legacy classes the 2026 stylesheet no longer ships — hstack/vstack/zstack/cover/cluster, section-* tones, prose, avatar, card-header/card-content/card-footer, blog, contact, cta, directory, feature, code-example, calendar-container/calendar-header, divider, size-*, aspect-ratio-*, scroll-reveal, and related modifiers. Its rules were carried over from the previous design-system stylesheet and resolve through aliases onto the 2026 --nu-* tokens, so tone/value changes upstream still flow through. Unlike the vendored file, nu-compat.css is maintained in this repo.
Component reference
All components are auto-imported. Every component accepts a class?: string prop that is merged onto its root element, and attributes fall through to the root. Wrappers emit the markup/classes expected by the 2026 stylesheet (plus compat-layer classes where noted above).
Actions & navigation
| Component | Root / classes | Props | Slots |
|---|---|---|---|
NButton |
.button (+ primary / secondary / contrast) on NuxtLink, <a>, or <button> |
variant, to, href, external (adds target="_blank" rel="noopener noreferrer"), type ('button'), disabled |
default |
NBreadcrumbs |
.breadcrumbs > nav[aria-label="Breadcrumb"] |
items: { label, to?, href? }[] (required) — first item renders the home icon, the current item renders <span aria-current="page"> |
— |
NSubnav |
nav.subnav > .container > .department-nav > ul (2026 markup; compat keeps the list visible on mobile) |
items: { label, to?, href?, current?, external? }[] |
default (custom items) |
Content & marketing blocks
| Component | Root / classes | Props | Slots |
|---|---|---|---|
NBanner |
.banner > .container |
title |
title, default, action |
NBlog |
.blog on NuxtLink / <a> / <article>; inner .hstack > img + .vstack.gap-0 |
to, href, title, meta, image, imageAlt |
title, meta, default |
NCard |
article.card |
aspectRatio?: 2 | 4 |
default |
NCardHeader |
img.card-header (with src) or .card-header element |
as, src, alt |
default |
NCardContent |
.card-content |
— | default |
NCardFooter |
footer.card-footer |
— | default |
NContact |
.contact |
name, title, email, phone, avatar, avatarAlt — email/phone render as mailto:/tel: links |
name, title, email, phone, default |
NCta |
.cta > .prose |
title, description |
title, description, default |
NDirectory |
.directory, columns in .hstack.top |
title |
title, default |
NFeature |
.feature — full-width h2, hr.divider, .layout > .main + .aside |
title, tag |
title, tag, aside, default |
NListing |
ul.listing, items get a full-cover .inset link labelled by the item title |
items: { title, href?, to?, description? }[] |
item-${i} (scoped { item }), default |
NQuote |
figure.quote > blockquote + figcaption |
cite, author, scrollReveal |
default, caption |
NStats |
.stats (+ accent-1…accent-4) |
accent, title (convenience; official markup nests NStat inside a heading via the default slot) |
title, default |
NStat |
span.stat |
value?: string | number |
default |
Layout primitives
| Component | Root / classes | Props | Slots |
|---|---|---|---|
NSection |
<section> (+ section-<tone>) |
tone: default, blue, alt, sky, skylight, powder, navy, midnight, evergreen, leaf, gold, sun, cloud, wisp, white; as (default 'section') |
default |
NContainer |
.container or .container-1….container-10 |
size?: 0–10 | 'default', as |
default |
NHStack |
.hstack |
reverse, top, between, stretch, noWrap (bool modifiers), as |
default |
NVStack |
.vstack |
as |
default |
NZStack |
.zstack |
as |
default |
NCenter |
.center |
as |
default |
NCover |
.cover |
as |
default |
NCluster |
.cluster |
as |
default |
NSticky |
.sticky |
as |
default |
NDivider |
hr.divider |
— | — |
NSpacer |
.spacer (aria-hidden) |
— | — |
UI & data
| Component | Root / classes | Props | Slots |
|---|---|---|---|
NAccordion |
.accordion, native <details>/<summary> |
items: { title, content?, open? }[] |
item-${i} (scoped { item }), default |
NAccordionItem |
<details> (inside NAccordion default slot) |
title (required), open |
default |
NAvatar |
img.avatar (+ size-1…size-5; no class = default 72px) |
src (required), alt, size?: 1–5 |
— |
NBadge |
span.badge (+ variant) |
variant: default, info, danger, warning, success, highlight |
default |
NCalendar |
.calendar-container + .calendar-header (compat) wrapping a table.cal month grid (2026) |
title, subtitle |
header, default, table |
NCode |
.code-example |
padded (default true; false adds p-0) |
default |
NTable |
.table > table |
caption |
caption, default |
NTooltip |
data-tooltip attributes, pure CSS |
text (required), position?: 'top' | 'bottom', as |
default (trigger) |
NTypography |
.prose |
as |
default |
Motion & carousel
| Component | Root / classes | Props | Slots |
|---|---|---|---|
NFadeIn |
.fade-in or .fade-in-children |
children?: boolean, as |
default |
NReel |
.reel[role="region" tabindex="0"] > .reel-container > ul.reel-list, controls in .reel-navigation |
showControls (default true) |
default (reel items) |
NReelItem |
li.reel-item + reel-content-light / reel-content-dark |
image, imageAlt, contentTone?: 'light' | 'dark' |
default |
NFadeIn runs its own IntersectionObserver (threshold 0.1, root margin 0px 0px -100px 0px) and adds .visible once when scrolled into view. The layer's client plugin does the same for plain .fade-in / .fade-in-children markup on every page and after each route navigation.
Composables
// Scoped fade-in observer (e.g. for dynamically rendered content)
const { observe } = useNFadeIn(rootElement?) // observe() re-scans
// Reel scroll state + controls (used internally by NReel)
const { canPrev, canNext, prev, next, update } = useNReel(scrollerElement)
prev() / next() smooth-scroll by one .reel-item width + gap; canPrev / canNext track scroll position and drive the disabled state of the controls.
Class-merge util
nClasses('button', variant && variant, { 'no-wrap': true }, props.class)
// clsx-style: strings passthrough, object keys when truthy, falsy skipped
Known differences from the official docs
NSubnavrenders a flat item list; the official<details>/<summary>dropdown submenus and mobile.department-nav-buttontoggle are not implemented (compat CSS keeps the list visible below 960px instead).NCalendaris a shell: it renders the 2026table.calgrid container, but event/day interactions (.day-link,.event-link) are markup you supply via thetableslot.NAvatarrenders<img>only; the officialsvg.avataricon variant is not implemented.NStats'stitleprop is a convenience not present in the official markup — prefer the default slot with the official nested-heading pattern (<h4><NStat>…</NStat> label</h4>).
Development
npm install --legacy-peer-deps
npx playwright install chromium # first time only (e2e / a11y)
npm run dev # docs-style playground at http://localhost:3000
npm run build # build playground
npm run generate # static-generate playground
npm run lint
The layer lives at the repo root. .playground/ extends .. so you can iterate on components against a real Nuxt app; every component has a demo page at /components/<slug> that mirrors the official docs page and links to it.
| Script | What it does |
|---|---|
dev |
nuxi dev .playground — playground with hot reload |
build / generate / preview |
Build / static-generate / preview the playground |
lint |
ESLint over the repo |
test / test:watch / test:coverage |
Vitest unit + a11y contracts |
test:e2e / test:e2e:ui |
Playwright smoke + axe scans |
test:a11y |
axe-core suite only |
test:all |
unit + e2e/a11y |
Testing
| Suite | Tooling | What it covers |
|---|---|---|
| Unit | Vitest, @nuxt/test-utils, happy-dom |
Key N* components, composables, a11y contracts |
| E2E | Playwright | Playground load, all 31 component anchors, smoke interactions |
| A11y | Playwright + @axe-core/playwright |
WCAG 2.1 A/AA serious/critical violations, landmarks, alt text, focus |
E2E uses port 3333 by default so it does not attach to an unrelated app on :3000. Override with E2E_PORT=…. Set E2E_REUSE=1 only if you intentionally want to reuse a running server.
Sync upstream CSS
By default the layer links https://www.northwestu.edu/assets/stylesheets/style-2026.css externally at runtime (see Configuration → Stylesheet). A pinned copy is also vendored at app/assets/css/nu-design-system.css for reference and for consumers who must self-host (CSP, offline builds). To refresh the pinned copy:
scripts/sync-css.sh
# or a specific URL:
scripts/sync-css.sh "https://www.northwestu.edu/assets/stylesheets/style-2027.css"
The script re-downloads the stylesheet and re-adds the attribution header. Do not hand-edit the vendored file without re-syncing upstream — kit-specific fixes belong in app/assets/css/nu-compat.css.
Publishing (npm)
The package publishes to the public npm registry as @northwestu/nuxt-nu-design-kit. prepublishOnly runs nuxt prepare .playground as a final sanity check.
Publishing is automated: pushing a GitHub Release runs .github/workflows/publish.yml, which installs, runs unit tests, and publishes with provenance. One-time setup: create a granular access token at npmjs.com with read/write access to the northwestu org's packages, and save it as the repo secret NPM_TOKEN.
Release checklist
# 1. Verify the gate
npm run test:all && npm run lint
# 2. Bump the version in package.json (and the app.config.ts debug version)
# 3. Verify the tarball contents
npm pack --dry-run
# 4. Commit, tag, push
git add -A
git commit -m "Release <version>: …"
git tag v<version>
git push origin main --tags
# 5. Publish — either create a GitHub Release (runs CI publish),
# or publish by hand (requires `npm login` as a northwestu org member):
npm publish --access public
After publishing, the package appears at https://www.npmjs.com/package/@northwestu/nuxt-nu-design-kit. Consumers install with no auth:
npm install @northwestu/nuxt-nu-design-kit
Package layout
@northwestu/nuxt-nu-design-kit
├── nuxt.config.ts # layer entry (main) — css, fonts, runtime config
├── app.config.ts # typed nDesignKit app config
├── app/
│ ├── assets/css/ # main.css (compat layer), pinned nu-design-system.css
│ ├── components/ # 40 N* Vue wrappers (auto-imported)
│ ├── composables/ # useNFadeIn, useNReel
│ ├── plugins/ # n-design-kit-head.ts (runtime head links), n-interactions.client.ts (fade-in observer)
│ └── utils/ # nClasses
├── scripts/sync-css.sh # re-vendor upstream CSS
├── tests/
│ ├── unit/ # Vitest component + a11y contracts
│ └── e2e/ # Playwright smoke + axe scans
├── vitest.config.ts
├── playwright.config.ts
└── .playground/ # local demo app (not published)
Accessibility notes
- Layer sets
html[lang="en"]by default. NListingoverlay links getaria-labelfrom the item title.NReelis a keyboard-focusable region (tabindex="0",role="region") with labelled prev/next controls.NBreadcrumbsusesnav[aria-label="Breadcrumb"]andaria-current="page"; decorative icons arearia-hidden.- Axe color-contrast findings from vendored design-system tokens are reported but not CI-gated until palette work lands upstream.
License
Apache-2.0 — see LICENSE. Source stylesheet: www.northwestu.edu/assets/stylesheets/style-2026.css.
Related Packages
Cover — revenexx design system for Nuxt. Distributed as a Nuxt layer: generic UI...
A reusable Nuxt foundation for content-driven happydesigns websites and applicat...
A premium card component library for Nuxt 4 with cinematic image filters, warm s...
tsp.tools design-system theme as a Nuxt layer (Nuxt UI v4 + Tailwind v4): amber-...
A delightful design-system engine and TypeScript harness for CSS: typed systems...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.1.0 | ^3.5.0 | Apache-2.0 |