@noundryfx/nounjs
v0.9.34
Published
Fast, simple reactive UI framework with Tailwind v4 integration
Maintainers
Readme
Nounjs
Fast, simple client-side UI framework with Tailwind CSS v4 integration.
Build responsive, reactive UIs quickly — optimized for human developers and LLM-assisted coding.
- Website: nounjs.com
- Hosting: deploy to
your-app.nounjs.appwith one command - Requirements: Node.js 18+ (or Bun 1.0+)
Documentation
- Getting Started - Quick start guide
- Theming - Light/dark mode & accent colors
- API Reference - Complete API documentation
- CLI Reference - Command-line interface guide
- Router Guide - Client-side routing
- UI Components - Pre-built component library
- Utilities - Built-in utility functions
- Architecture - Internal design & patterns
- Troubleshooting - Common issues & solutions
- Performance - Optimization guide
Editor Support
- VS Code Extension - Syntax highlighting, snippets, and IntelliSense for
.nounfiles. See vscode-extension/ for installation.
Features
- Tree-shakeable - Pay only for what you use (~32KB minimal, ~41KB typical)
- CSP-compliant - Safe expression evaluator (no eval)
- HTML-first - Components are just HTML with directives
- No virtual DOM - Direct DOM updates for speed
- Built-in Theming - Light/dark mode + 8 accent colors with zero config
- Tailwind CSS v4 - First-class Tailwind integration with custom component classes
- Single-file components -
.nounfiles compile to JavaScript - Robust Router - URL-first state management with guards
- TypeScript - Full type definitions included
- CLI - Generate projects, components, pages instantly
Bundle Size
Nounjs is fully tree-shakeable. Use named imports for smallest bundles:
// ✅ Tree-shakeable - only pays for what you use
import { Component, mount } from '@noundryfx/nounjs/core' // ~32KB
import { Component, mount, createRouter } from '@noundryfx/nounjs/core' // ~37KB
import { Component, mount, createStore } from '@noundryfx/nounjs/core' // ~36KB
// ❌ Imports everything - no tree-shaking
import Noun from '@noundryfx/nounjs' // 160KB| What You Import | Size |
|-----------------|------|
| Component + mount | ~32KB |
| + createRouter | ~37KB |
| + createStore | ~36KB |
| + Router + Store | ~41KB |
| Full bundle (no tree-shaking) | ~160KB |
Installation
# Install globally
npm install -g @noundryfx/nounjs
# Or use npx
npx @noundryfx/nounjs create my-appQuick Start
Create with Nounjs Tailwind (default)
npx @noundryfx/nounjs create my-app
cd my-app
bun install
noun devProduction Build
noun prod # Build for production
noun publish my-app # Deploy to my-app.nounjs.appSingle-File Components (.noun files)
Create src/pages/counter.noun:
<template>
<div class="p-4">
<p class="text-lg">Count: { count }</p>
<button @click="increment" class="btn">Add</button>
</div>
</template>
<script>
export default {
count: 0,
increment() {
this.count++ // DOM updates automatically - no update() needed!
}
}
</script>
<style>
/* Scoped styles */
</style>Compile to JavaScript:
noun compileJavaScript Components
import { Component, mount } from '@noundryfx/nounjs'
class Counter extends Component {
template = `
<div class="p-4">
<p class="text-lg">Count: { count }</p>
<button @click="increment" class="btn">Add</button>
</div>
`
count = 0
increment() {
this.count++ // Reactivity is automatic!
}
}
mount('#app', Counter)Automatic Reactivity
Nounjs uses Proxy-based reactivity - no manual update() calls needed:
// All state changes automatically update the DOM
this.count++ // ✓ Auto-updates
this.name = 'John' // ✓ Auto-updates
this.items.push(item) // ✓ Array mutations too
this.items = newArray // ✓ Reassignment works
// Use :model for two-way form binding
<input :model="email" /> // ✓ Auto-syncs both waysTemplate Directives
| Directive | Description |
|-----------|-------------|
| { expr } | Text interpolation |
| {{ expr }} | HTML interpolation (raw) |
| :attr="expr" | Dynamic attribute |
| :model="prop" | Two-way binding |
| @event="handler" | Event binding |
| :if="expr" | Conditional rendering |
| :for="item of items" | List rendering |
| ref="name" | Element reference |
Event Modifiers
<button @click.prevent="submit">Submit</button>
<input @keyup.enter="search" />
<div @click.stop.once="handler">Click once</div>Component Events
Child component emits:
// user-card.noun
handleSelect() {
this.emit('select', { user: this.user })
}Parent listens:
const card = mount(el, UserCard, { user })
card.on('select', (data) => console.log(data.user))Router
import { createRouter } from '@noundryfx/nounjs/router'
const router = createRouter({ mode: 'hash' })
router
.route('/', () => mount('#app', Home))
.route('/users/:id', (to) => mount('#app', User, { id: to.params.id }))
.route('/admin', (to) => mount('#app', Admin), {
meta: { requiresAuth: true }
})
.guard(async (to, from, next) => {
if (to.meta.requiresAuth && !isLoggedIn()) {
next('/login')
} else {
next()
}
})
.start()CLI Commands
Project
noun create <name> [options] # Create new project
--style <style> # 'tailwind' (default)
noun dev # Compile + Build + Serve + Watch (all-in-one)
noun prod # Production build (compile + minify + bundle)
noun serve [port] # Start static server onlyBuild
noun compile # Compile .noun files to JS
noun compile --watch # Watch .noun files only
noun build # Build CSS + JS
noun build:css # Build Tailwind CSS only
noun build:js # Bundle JavaScript only
noun watch # Watch everything (noun, CSS, JS)
noun watch:css # Watch CSS onlyMaintenance
noun clean # Remove build artifacts (dist/, compiled/)
noun clean --all # Also remove node_modules
noun clean --dry-run # Preview what would be deleted
noun refresh # Update Nounjs core to latest version
noun doctor # Diagnose project issuesDeployment
noun publish <app-name> # Deploy to app-name.nounjs.app
noun publish --dry-run # Preview without deploying
noun apps # List deployed apps
noun delete <app-name> # Delete deployed appGenerate
noun generate component <name> # Basic component
noun generate page <name> [type] # Page (basic, list, form, detail, login)
noun g component Button # Shorthand
noun g page Dashboard list # List pageComponents
noun list # List available UI components
noun add <component> # Add component to project
noun add button card table # Add multiple
noun add --all # Add all componentsStyling Options
Nounjs Tailwind (default)
Custom Tailwind-based component classes:
<button class="noun-btn-primary">Primary</button>
<div class="noun-card">
<div class="noun-card-body">Content</div>
</div>
<span class="noun-badge-success">Active</span>
<input class="noun-input" placeholder="Email" />Theming
Nounjs includes a powerful theming system with zero configuration:
import { theme } from '@noundryfx/nounjs'
// Dark mode
theme.set('dark') // Force dark
theme.set('light') // Force light
theme.set('system') // Follow OS preference
theme.toggle() // Toggle light/dark
// Accent colors (8 presets)
theme.accent('emerald') // Green
theme.accent('rose') // Pink
theme.accent('indigo') // Purple
// Get current state
const { mode, accent } = theme.current()
// Listen for changes
theme.onChange(({ mode, accent }) => {
console.log('Theme changed:', mode, accent)
})All noun-* components automatically adapt to theme changes. Use CSS variables in your own components:
.my-card {
background: var(--noun-bg-elevated);
color: var(--noun-text);
border: 1px solid var(--noun-border);
}
.my-button {
background: var(--noun-primary);
color: var(--noun-text-inverse);
}See the Theming Guide for complete documentation.
Project Structure
my-app/
├── index.html # Entry HTML
├── app.js # Main application
├── noun.config.json # Nounjs configuration
├── tailwind.config.js # Tailwind configuration
├── src/
│ ├── pages/ # .noun single-file components
│ ├── compiled/ # Compiled JS (generated)
│ └── styles/
│ └── globals.css # Tailwind + custom styles
├── build/ # Build artifacts (gitignored)
│ ├── noun.esm.js # Nounjs runtime (for bundler)
│ └── router.esm.js # Router module (for bundler)
├── dist/ # Distribution output (deployable)
│ ├── styles.css # Built CSS
│ └── app.js # Bundled JS (includes Nounjs)
└── package.jsonFolder purposes:
build/- Local build artifacts for bundler resolution (gitignored)dist/- Distribution output ready for deployment
Configuration
noun.config.json:
{
"style": "tailwind",
"compile": {
"input": "src/pages",
"output": "src/compiled"
},
"bundle": {
"entry": "app.js",
"output": "dist/app.js"
}
}API Integration
Nounjs excels at building apps that interact with REST APIs:
// Authentication service
const auth = {
token: null,
async login(email, password) {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password })
})
this.token = (await response.json()).token
}
}
// API client with auth headers
const api = {
async request(endpoint, options = {}) {
return fetch(`/api${endpoint}`, {
...options,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${auth.token}`,
...options.headers
}
})
},
get: (url) => api.request(url),
post: (url, data) => api.request(url, { method: 'POST', body: JSON.stringify(data) })
}
// Use in components
class UserList extends Component {
users = []
async mounted() {
this.users = await api.get('/users').then(r => r.json())
// No update() needed - assigning to this.users triggers reactivity
}
}Browser Support
Modern browsers (Chrome, Firefox, Safari, Edge). No IE11 support.
Examples
Complete example apps live in the examples/ directory of the GitHub repo:
kitchen-sink/— Component showcase with all UI elements (Live Demo)tailwind-app/— Tailwind CSS styled app with auth & CRUD (Live Demo)full-app/— Complete app with widgets and settings
Community & Support
- Website — nounjs.com
- GitHub — github.com/noundry/nounjs
- Issues — report bugs or request features
- npm — npmjs.com/package/nounjs
Contributing
Contributions are welcome! Please see the Architecture Guide for information about the codebase structure.
License
MIT © Noundry
