Nixi is a high-performance Lua web framework that ships zero-byte logic to the client using HTMX. Built for production with native CSS DSL, pre-built UI components, and excellent developer experience.
| Feature | React | Vue | Next.js | Nixi |
|---|---|---|---|---|
| Zero build step | ❌ npm build | ❌ | ❌ | ✅ |
| Pure Lua | ❌ | ❌ | ❌ | ✅ |
| HTMX-native | ❌ | ❌ | ❌ | ✅ |
| Web + Desktop | ❌ windows or macOS | ❌ | ❌ | ✅ |
| Native CSS DSL | ❌ | ❌ | ❌ | ✅ |
| CLI tool | ✅ |
- Zero Client JavaScript: HTMX for reactive UIs without writing JS
- Pure Lua CSS: Define styles in Lua with the CSS DSL
- UI Component Library: Pre-built components (Button, Input, Card, Modal, Toast, etc.)
- Theme Support: Tokyo Night, Dracula, Catppuccin, and custom themes
- Type Safety: LuaLS annotations for IDE autocompletion
- GTK Desktop: Same code, desktop GUI with GTK4 bindings
- Production Ready: Security headers, static file serving, production server
- Standalone: No external dependencies required (uses FFI when libraries unavailable)
# Option 1: Via Nix (recommended)
git clone https://github.com/ijadux2/nixi.git
cd nixi
nix develop # Enter dev environment
# Option 2: Via Luarocks
luarocks install nixi
# Option 3: Manual
git clone https://github.com/ijadux2/nixi.git
cd nixi
./install.sh # Install globally# Create new project
nixi new myapp
cd myapp
# Start development server
nixi dev
# Or run directly
lua server.luaVisit http://127.0.0.1:3000
---@meta
package.path = "src/nixi/?.lua;" .. package.path
local Nixi = require("init")
_G.Nixi = Nixi
local app = Nixi.new({
host = "127.0.0.1",
port = 3000,
})
app:get("/", function(ctx)
return Nixi.html([[
<html>
<head><title>Hello</title></head>
<body>
<h1>Hello from Nixi!</h1>
</body>
</html>
]])
end)
return applocal style = require("style")
Nixi.style = style
-- Define CSS variables
Nixi.style.variable("primary", "#7aa2f7")
Nixi.style.variable("bg", "#1a1b26")
-- Define rules
Nixi.style.rule("body", {
backgroundColor = "var(--bg)",
color = "#fff",
})
Nixi.style.rule(".btn", {
padding = "10px 20px",
borderRadius = "8px",
backgroundColor = "var(--primary)",
})
-- Apply full theme config
Nixi.style.applyTheme({
colors = {
background = "#1a1b26",
primary = "#7aa2f7",
},
fonts = {
sans = "system-ui, sans-serif",
},
})
-- Generate CSS
local css = Nixi.style.toCSS()-- Generate spacing utilities (margin: 0.25rem to 4rem)
for i = 1, 16 do
Nixi.style.rule(".m-" .. i, {
margin = (i * 0.25) .. "rem"
})
Nixi.style.rule(".p-" .. i, {
padding = (i * 0.25) .. "rem"
})
end
-- Generate flexbox utilities
for _, align in ipairs({"start", "center", "end", "stretch", "baseline"}) do
Nixi.style.rule(".items-" .. align, {
alignItems = align == "start" and "flex-start" or align == "end" and "flex-end" or align
})
end
-- Generate color utilities
local colors = {
{ name = "primary", value = "#7aa2f7" },
{ name = "danger", value = "#f7768e" },
{ name = "success", value = "#9ece6a" },
}
for _, color in ipairs(colors) do
Nixi.style.rule(".text-" .. color.name, { color = color.value })
Nixi.style.rule(".bg-" .. color.name, { backgroundColor = color.value })
end
-- Generate responsive width utilities
for _, width in ipairs({25, 50, 75, 100}) do
Nixi.style.rule(".w-" .. width, { width = width .. "%" })
endThis generates hundreds of utility classes from just a few lines of Lua code!
local UI = require("nixi.ui")
-- Button with variants
UI.Button.render({ variant = "primary", children = "Click Me" })
UI.Button.render({ variant = "outline", children = "Cancel" })
-- Form inputs
UI.Input.render({ label = "Email", type = "email", placeholder = "you@example.com" })
UI.Select.render({ label = "Language", options = {{ value = "lua", label = "Lua" }} })
-- Cards
UI.Card.render({
title = "Feature",
children = "Some content",
footer = UI.Button.render({ variant = "primary", children = "Learn More" })
})
-- Modals, Tabs, Toast notifications...app:get("/api/counter", function(ctx)
local count = (ctx.session.count or 0) + 1
ctx.session.count = count
return Nixi.html('<span id="count">' .. count .. '</span>')
end)<button hx-get="/api/counter" hx-target="#count" hx-swap="innerHTML">
Increment
</button>-- Tables
Nixi.table({'Name', 'Age'}, {{'John', '25'}, {'Jane', '30'}})
-- From data (auto-generates headers)
local data = {
{name = 'John', age = '25', city = 'NYC'},
{name = 'Jane', age = '30', city = 'LA'},
}
Nixi.tableFromData(data) -- Auto-generates headers from keys
-- Images
Nixi.img('photo.jpg', 'A photo')
Nixi.figure('photo.jpg', 'Caption text')
-- Gallery
Nixi.gallery({
{src = 'img1.jpg', alt = 'Image 1', title = 'First'},
{src = 'img2.jpg', alt = 'Image 2', title = 'Second'},
})
-- Navigation
Nixi.nav({
{href = '/', text = 'Home', active = true},
{href = '/about', text = 'About'},
})
-- Forms
Nixi.form('/submit', 'post',
Nixi.label('email', 'Email:') ..
Nixi.input({type = 'email', name = 'email'}) ..
Nixi.select('country', {
{value = 'us', label = 'USA'},
{value = 'uk', label = 'UK'},
})
)-- nixi.lua config
return {
name = "My Site",
theme = { name = "tokyoNight" },
build = {
output = "dist",
skipRoutes = {"/api/*"},
},
}# Build static site
nixi build
# Output goes to dist/nixi new <name> # Create new project
nixi dev [port] # Start development server
nixi build [out] # Build static site (default: dist/)
nixi build -c # Clean build
nixi style [file] # Compile CSS to file
nixi generate c <n> # Generate component
nixi generate r <p> # Generate route
nixi generate m <n> # Generate middleware
nixi doctor # Check system requirements
nixi add <package> # Install luarocks package
nixi init # Initialize in current folder
nixi clean # Clean build artifacts
nixi gtk # Run GTK desktop appmyapp/
├── src/
│ ├── nixi/ # Framework (symlinked)
│ │ ├── init.lua # Core router
│ │ ├── style.lua # CSS DSL
│ │ ├── htmx.lua # HTMX bindings
│ │ ├── ui/ # UI components
│ │ │ ├── button.lua
│ │ │ ├── input.lua
│ │ │ ├── card.lua
│ │ │ └── ...
│ │ └── gtk.lua # Desktop bindings
│ └── app.lua # Your application
├── routes/ # File-based routes
├── layouts/ # Layout templates
├── public/ # Static files
├── server.lua # Development server
└── prod-server.lua # Production server
| Component | Description |
|---|---|
Button |
Primary, Secondary, Outline, Ghost, Danger variants |
Input |
Text, Email, Password with labels and validation |
Select |
Dropdown with placeholder support |
Checkbox |
Styled checkboxes with labels |
Radio |
Radio button groups |
Card |
Default, Bordered, Elevated with hover effects |
Modal |
Dialog with header, body, footer |
Toast |
Success, Error, Warning, Info notifications |
Loader |
Spinner, Dots, Bars, Pulse animations |
ProgressBar |
Progress indicators with variants |
Tabs |
Default, Pills, Underline variants |
Badge |
Status indicators with variants |
Tag |
Removable tags |
local theme = require("nixi.ui.theme")
-- Use predefined theme
local colors = theme.getTheme("tokyoNight").colors
theme.applyTheme(colors)
-- Inject all component styles
theme.injectAllStyles(colors)Available themes:
- Tokyo Night - Dark blue theme
- Dracula - Purple accent theme
- GitHub Dark - GitHub's dark mode
- Catppuccin Mocha - Soft pastel theme
| Variable | Default | Description |
|---|---|---|
NIXI_HOST |
127.0.0.1 |
Server bind address |
NIXI_PORT |
3000 |
Server port |
NIXI_ENV |
development |
Environment mode |
- Lua 5.1+ or LuaJIT
Nixi works standalone using LuaJIT FFI, but these improve functionality:
# Optional - for better compatibility
luarocks install luasocket luafilesystemWithout these, Nixi uses native FFI calls for:
- File operations: Directory scanning, file attributes, mkdir
- HTTP server: TCP sockets, binding, accepting connections
MIT License - see LICENSE for details.
Copyright (c) 2024 ijadux2
Made with ❤️ by ijadux2