Ultra-lightweight, type-safe SSR components with DOM-native state management and hybrid reactivity.
- 🌐 SSR-first, JSX-always rendering
- 🧭 Light Functional Programming: types-first, pure functions, Result<T,E>, no classes
- 🧩 DOM-native state management: classes, data-* attributes, element content, CSS custom properties
- 🔒 Composition-Only Pattern: Apps compose pre-styled components, enforcing UI consistency
- 🎨 Zero Style Conflicts: No custom CSS in apps
- 🕵️ HTMX encapsulated via component APIs (no hx-* in application code)
- 📦 Component-colocated API, styles, and reactivity
- 🔧 Type-safe end-to-end with strict TypeScript
- 📚 50+ pre-styled components with rich variant APIs; progressive enhancement optional (zero framework runtime)
# Local clone (recommended)
git clone https://github.com/srdjan/ui-lib.git
cd ui-lib
# Dev server & tasks
deno task start # type-check then start the Todo demo
deno task serve # start the Todo demo directly
deno task bundle:state # emits dist/ui-lib-state.js for optional client helpers
import { defineComponent, h, render } from "ui-lib/mod.ts";
import { Card } from "ui-lib/components";
// Application components compose pre-styled library components
defineComponent("user-card", {
render: ({ name = "Guest", role = "User" }) => (
<card variant="elevated" padding="lg">
<h2>{name}</h2>
<p>{role}</p>
</card>
),
});
// Use it (JSX + render to produce H
7440
TML on the server)
const html = render(<user-card name="Alice" role="Admin" />);
Why Composition-Only? Applications cannot add custom styles when using
mod.ts
. This enforces UI consistency, reduces code by 94%, and ensures all apps using ui-lib have a uniform look. Custom styling is reserved for library component development (seelib/internal.ts
).
Note: In application code, call
render(<Component />)
to produce HTML.renderComponent
is still available for low-level access when needed.
import { boolean, defineComponent, h, number, string } from "ui-lib";
import { Button, Card } from "ui-lib/components";
defineComponent("counter", {
render: ({
label = string("Count"),
value = number(0),
disabled = boolean(false),
}) => (
<card variant="default" padding="md">
<span>{label}: {value}</span>
<button variant="primary" size="md" disabled={disabled}>
Increment
</button>
</card>
),
});
// Type-safe usage (JSX-only in app code)
const html = <counter label="Items" value={5} />;
Applications build UIs by composing pre-styled library components with variants:
import { Alert, Badge, Button, Card } from "ui-lib/components";
const Page = () => (
<>
<alert variant="success">
Operation completed!
</alert>
<card variant="elevated" padding="lg">
<h2>
Welcome <badge variant="primary">New</badge>
</h2>
<p>Get started with ui-lib's 50+ components</p>
<button variant="primary" size="lg">
Get Started
</button>
</card>
</>
);
All components provide rich variant APIs (primary, secondary, success, danger, etc.) for customization without CSS.
Components define APIs with HTTP method helpers, keeping HTMX completely hidden from application code. Applications compose library components with native spread operators:
import { defineComponent, del, h, post } from "ui-lib/mod.ts";
defineComponent("todo-item", {
api: {
toggle: post("/api/todos/:id/toggle", toggleHandler),
remove: del("/api/todos/:id", deleteHandler),
},
render: ({ todo }, api) => (
<item completed={todo.completed}>
<input type="checkbox" {...api!.toggle(todo.id)} />
<span>{todo.text}</span>
<button {...api!.remove(todo.id)}>Delete</button>
</item>
),
});
Key Benefits:
- ✅ Zero HTMX in app code - All
hx-*
attributes generated internally - ✅ Zero custom CSS - Library's
<item>
component provides all styling - ✅ Ergonomic spread syntax - Use
{...api!.action(id)}
directly in JSX - ✅ Type-safe APIs - HTTP methods (
post
,del
,get
,patch
,put
) with route params - ✅ Automatic route registration - Call
registerComponentApi("todo-item", router)
once - ✅ Children support - Library components accept custom children with native elements
Available HTTP helpers: get()
, post()
, patch()
, put()
, del()
(aliased: remove
, create
)
Instant visual updates via CSS custom properties:
defineComponent("theme-toggle", {
reactive: {
css: {
"--theme": "data-theme",
},
},
render: () => `<button >Toggle Theme</button>`,
});
Cross-component state synchronization:
defineComponent("cart", {
reactive: {
state: {
"cart-count": "data-count",
},
},
render: () => `
<div data-count="0">
Cart Items: <span x-text="count">0</span>
</div>
`,
});
Component-to-component messaging:
defineComponent("notification", {
reactive: {
on: {
"user:login": "handleLogin",
},
},
render: () => `<div id="notification"></div>`,
});
ui-lib includes 50+ production-ready components:
- Layout: AppLayout, Navbar, Sidebar, MainContent
- Forms: Input, Select, Textarea, Checkbox, Radio, Switch
- Buttons: Button, ButtonGroup, IconButton
- Data: Table, Card, List, Tree
- Feedback: Alert, Toast, Progress, Skeleton
- Overlays: Modal, Drawer, Popover, Tooltip
- Navigation: Tabs, Breadcrumb, Pagination, Stepper
- Display: Avatar, Badge, Tag, Chip
Run the showcase server to see all components in action:
# Start the Todo app demo
deno task serve
# Open http://localhost:8080
Repo layout (examples)
examples/
└── todo-app/
├── server-custom.tsx # Custom components demo
├── server-library.tsx # Library components demo
├── api/ # Handlers, types, repository
└── components/ # SSR components with colocated API/styles/reactivity
- SSR rendering: ~0.5ms per component
- Zero client runtime: No JavaScript framework needed
- Tiny footprint: < 10KB for optional client enhancements
- Streaming: Built-in support for streaming responses
# Type check
deno task check
# Run tests
deno task test
# Format code
deno task fmt
# Run examples
deno task serve
# Build documentation
deno task docs
ui-lib reimagines web components by embracing the platform:
- State belongs in the DOM - Not in JavaScript memory
- CSS is the styling language - Not JavaScript objects (for library components)
- HTML is the structure - Not virtual DOM trees
- Progressive enhancement - Not hydration
- Server-first - Not client-first with SSR bolted on
- Composition over customization - Apps compose pre-styled components with variants
While applications use the composition-only pattern, library components have full styling capabilities:
// lib/components/my-component.ts
import { css, defineComponent } from "../../internal.ts";
defineComponent("my-component", {
styles: css({
padding: "1rem",
backgroundColor: "var(--surface-bg)",
// Full CSS-in-TS capabilities
}),
render: ({ variant = "default" }) => (
<div class={`my-component my-component--${variant}`}>
{/* component content */}
</div>
),
});
Library components import from lib/internal.ts
which provides unrestricted
access to:
- Full
defineComponent
with styles - CSS-in-TS system (
css
,createTheme
,composeStyles
) - Theme system and design utilities
- All internal utilities
See Contributing Guide for details on developing library components.
MIT License - see LICENSE file for details.
Contributions are welcome! Please read our Contributing Guide for details.