@northwestu/nuxt-nu-design-kit

Northwest University marketing design system as a Nuxt 4 layer — tokens, CSS components, and Vue wrappers.
192
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
Maintainer: tim-mushen

@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.css at 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.css restores 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> + bundled app/assets/css/main.css → nu-compat.css)
  • All N* components, auto-imported
  • useNFadeIn / useNReel composables and the nClasses util, auto-imported
  • A client plugin that activates .fade-in / .fade-in-children scroll 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

  • NSubnav renders a flat item list; the official <details>/<summary> dropdown submenus and mobile .department-nav-button toggle are not implemented (compat CSS keeps the list visible below 960px instead).
  • NCalendar is a shell: it renders the 2026 table.cal grid container, but event/day interactions (.day-link, .event-link) are markup you supply via the table slot.
  • NAvatar renders <img> only; the official svg.avatar icon variant is not implemented.
  • NStats's title prop 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.
  • NListing overlay links get aria-label from the item title.
  • NReel is a keyboard-focusable region (tabindex="0", role="region") with labelled prev/next controls.
  • NBreadcrumbs uses nav[aria-label="Breadcrumb"] and aria-current="page"; decorative icons are aria-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

@revenexx/cover

Cover — revenexx design system for Nuxt. Distributed as a Nuxt layer: generic UI...

15,376
@happydesigns/ui

A reusable Nuxt foundation for content-driven happydesigns websites and applicat...

3,059 1
@xenterprises/nuxt-x-cards

A premium card component library for Nuxt 4 with cinematic image filters, warm s...

2,272
@thespielplatz/tsp-tools-theme

tsp.tools design-system theme as a Nuxt layer (Nuxt UI v4 + Tailwind v4): amber-...

3,240
@mszr/vanity

A delightful design-system engine and TypeScript harness for CSS: typed systems...

1,999 1

Version History

Version Released Vue License
0.1.0 ^3.5.0 Apache-2.0