@enya-learning/nova
v0.4.3
Published
Enya Learning's component library for building modern web applications.
Readme
@enya-learning/nova
Enya Learning's component library for building modern web applications.
Nova provides accessible, themeable UI components built on Base UI primitives and styled with Tailwind CSS v4. It handles keyboard navigation, focus management, and ARIA attributes so you can build accessible applications without thinking through every detail.
Installation
pnpm add @enya-learning/novaPeer Dependencies
pnpm add react react-domImport Components
// Main package import
import { Button, Input, Dialog } from "@enya-learning/nova"
// Granular imports (recommended for tree-shaking)
import { Button } from "@enya-learning/nova/components/button"Import Styles
For Tailwind CSS v4 Users
Tailwind CSS v4 does not scan node_modules/ by default. Add a @source
directive so Tailwind discovers the utility classes Nova uses — without this,
components may render with missing styles.
In your main CSS file:
@source "../node_modules/@enya-learning/nova/dist/**/*.{js,jsx,ts,tsx}";
@import "@enya-learning/nova/styles";
@import "tailwindcss";Import order matters — @enya-learning/nova/styles must come before
@import "tailwindcss" so Nova's @theme tokens are registered first.
Note: The
@sourcepath is relative to your CSS file. Adjust it based on your project structure.
Global CSS (optional)
Nova ships a globals.css for base resets and font loading:
@import "@enya-learning/nova/globals.css";Styles architecture
Nova's CSS is split across four files with distinct responsibilities:
| File | Role |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| nova-binding.css | Registers the raw palette into Tailwind's @theme — every nova-* colour stop and utilities like --ease-bounce. This is what makes bg-nova-sky-500 work as a class. |
| theme-nova.css | Semantic tokens — maps palette stops to meaning (--color-nova-brand, --color-nova-success, --text-color-nova-subtle, etc.) with light-dark() for automatic light/dark switching. |
| nova.css | The bridge to shadcn — remaps shadcn's generic variables (--primary, --background, --destructive, etc.) onto nova semantic tokens so shadcn components pick up nova colours without modification. |
| globals.css | App entry point — imports Tailwind, fonts, shadcn base, and nova.css. Also sets @theme inline to wire shadcn colour vars into Tailwind's class system, defines the dark variant, and applies base body/border styles. |
Import chain:
globals.css → nova.css → nova-binding.css → theme-nova.cssWhere to make changes:
- Adding a new colour stop →
nova-binding.css - Adding or changing what a colour means (e.g. a new
nova-brand-subtlesurface) →theme-nova.css - Swapping which palette scale powers a semantic role (e.g. changing the brand
variant) →
nova-binding.cssbrand alias block - Mapping a new shadcn variable to nova →
nova.css
Base UI Primitives
Nova is built on Base UI. For advanced use cases
requiring fine-grained control, reach into your own @base-ui/react dependency
directly:
import { Popover } from "@base-ui/react"Prefer styled Nova components when available — use Base UI primitives only for custom components not yet in Nova.
Table of contents
- Overview
- Prerequisites
- Development workflows
- Adding a new component
- Writing stories
- Generating docs
- Testing and quality
- Publishing
Overview
nova/
├── apps/
│ └── web/ Astro documentation site (localhost:4321)
└── packages/
└── nova/ This package — the component library
├── src/
│ ├── components/ One folder per component
│ ├── styles/ Design tokens and global CSS
│ ├── lib/ Utilities (cn, etc.)
│ └── index.ts Library barrel export
└── .storybook/ Storybook configurationThree moving parts:
| Tool | Purpose | Port |
| ---------------------------- | ------------------------------------------------- | ---- |
| Storybook | Isolated component development & interactive docs | 6006 |
| pnpm dev | Vite watch build — feeds the Astro docs site | — |
| Astro docs site (apps/web) | Static documentation website | 4321 |
Storybook is the primary development environment. The Astro docs site is for publishing static reference documentation.
Prerequisites
- Node ≥ 22.12
- pnpm 10
# From the monorepo root — installs all workspaces
pnpm installDevelopment workflows
Option A — Storybook (recommended for component work)
The fastest way to build and test components in isolation.
# From packages/nova
pnpm storybook
# → http://localhost:6006What you get:
- Full HMR with React Fast Refresh
- Interactive controls for every prop
- Auto-generated prop tables from TypeScript types (
autodocstag) - Light/dark mode toggle (sets
data-mode="dark"on the root element) - Accessibility panel
Option B — Watch build + Astro docs site
Use this when you need to see your changes reflected in the actual documentation website.
# Terminal 1 — rebuild nova on every save (~400ms)
cd packages/nova
pnpm dev
# Terminal 2 — Astro dev server
cd apps/web
pnpm dev
# → http://localhost:4321Refresh the browser after a nova rebuild to see changes.
Adding a new component
Use the generator to scaffold the files and wire up the exports in one step:
pnpm --filter @enya-learning/nova new-componentThe generator creates the component file, barrel index, and story, then
automatically adds the barrel export to src/index.ts and the build entry to
vite.config.ts. The package.json exports use a wildcard ("./components/*")
— no manual edit needed.
After running the generator, complete these final steps manually:
1. Implement the component
Fill in src/components/{name}/{name}.tsx — the generator creates a minimal
<div> shell.
2. Write a story
The generator creates a stub story with a single Default export. Add variant
and state stories. See Writing stories below.
3. Generate the docs page
pnpm generate-docsThis creates apps/web/src/content/components/{name}.mdx from your story file.
Commit it.
Reference: what the generator does
For manual setups or debugging, here is what pnpm new-component automates:
Creates:
src/components/{name}/
├── {name}.tsx component shell
├── {name}.stories.tsx Storybook story stub
└── index.ts barrel re-export (export * from "./{name}")Modifies src/index.ts — appends before the utils export:
export * from "./components/{name}"Modifies vite.config.ts — inserts before // ADD_COMPONENT_ENTRY_HERE:
"components/{name}": resolve(__dirname, "src/components/{name}/index.ts"),Writing stories
Stories live alongside their component:
src/components/button/
├── button.tsx
├── button.stories.tsx ← story file
└── index.tsCanonical pattern
import type { Meta, StoryObj } from "@storybook/react"
import { Button } from "./button"
// Meta: describes the component and sets defaults shared across all stories
const meta: Meta<typeof Button> = {
title: "Components/Button",
component: Button,
tags: ["autodocs"], // enables the auto-generated Docs tab
args: { children: "Click me" }, // default prop values for all stories
}
export default meta
type Story = StoryObj<typeof meta>
// Each named export is one story (one state of the component)
export const Default: Story = {}
export const Outline: Story = {
args: { variant: "outline" }, // overrides meta.args
}
export const Disabled: Story = {
args: { disabled: true },
}Rules of thumb:
- Set shared defaults in
meta.args(e.g.children,placeholder). Per-storyargsmerge on top. - Add
tags: ["autodocs"]to every meta object — this activates the auto-generated props table. - Give every variant and state its own named export so it appears in the sidebar.
- For components that render void HTML elements (e.g.
Input,Checkbox), do not setchildreninargs— the generator produces self-closing JSX. - Use a
render:function for multi-component showcase stories (AllVariants, AllSizes). The doc generator skips these.
Dark mode
The Storybook toolbar has a light/dark toggle. It sets data-mode="dark" on the
HTML root, activating Nova's dark: Tailwind variant. All components respond
automatically — no extra work needed in stories.
Generating docs
The generate-docs script reads every *.stories.tsx file, extracts prop types
and story examples, and writes a .mdx file to the Astro docs site.
# From packages/nova
pnpm generate-docsOutput: apps/web/src/content/components/{name}.mdx
Re-run this whenever you:
- Add a new component story
- Add or rename stories
- Update
meta.args
The generated MDX files are committed to the repo so the Astro site can build without running the script in CI.
Testing and quality
# Unit tests (vitest)
pnpm test # single run
pnpm test:watch # watch mode
# TypeScript
pnpm typecheck
# Lint
pnpm lint
# Format
pnpm formatRun all checks from the monorepo root:
pnpm --filter @enya-learning/nova test
pnpm typecheck # runs across all workspaces via turbo
pnpm lintPublishing
Nova is published to the GitHub npm registry (npm.pkg.github.com). Publishing
requires a GitHub Personal Access Token with packages:write scope in your
~/.npmrc:
//npm.pkg.github.com/:_authToken=YOUR_TOKENRelease flow
1. Create a changeset — describe what changed and choose a semver bump:
# From the monorepo root
pnpm changesetSelect @enya-learning/nova, choose patch / minor / major, write a one-line
description. This creates a .changeset/*.md file — commit it with your feature
branch.
Semver guide: | Change type | Bump | |-------------|------| | Bug fix, style
tweak | patch | | New component, new prop, new variant | minor | | Removed
export, renamed prop, breaking API change | major |
2. Bump the version — consumes all pending changesets and updates
CHANGELOG.md:
pnpm version3. Build and publish:
pnpm release
# → builds packages/nova, then runs changeset publish4. Push the version commit and tags:
git push && git push --tags