https://github.com/srdjan/hsx
SSR-only JSX/TSX renderer for Deno that hides HTMX away :)
https://github.com/srdjan/hsx
deno htmx ssr typescript ui
Last synced: 5 months ago
JSON representation
SSR-only JSX/TSX renderer for Deno that hides HTMX away :)
- Host: GitHub
- URL: https://github.com/srdjan/hsx
- Owner: srdjan
- License: mit
- Created: 2025-12-02T23:09:57.000Z (7 months ago)
- Default Branch: main
- Last Pushed: 2025-12-28T21:53:02.000Z (6 months ago)
- Last Synced: 2026-01-11T14:04:40.188Z (6 months ago)
- Topics: deno, htmx, ssr, typescript, ui
- Language: TypeScript
- Homepage:
- Size: 281 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# HSX is love, HSX is life
First things, first... What the hack does HSX stand for? I'll say it's '**H**TML
for **S**erver-Side e**X**tensions'. :0
But, honestly, I prefer: **H**TMX **S**laps **X**tremely :)
SSR-only JSX/TSX renderer for Deno that hides HTMX-style attributes away during the rendering process, and compiles them to `hx-*` attributes.
> Disclaimer: this was a quick hack in my free time, held together by vibe
> coding and espresso. I like it a lot, but consider it an early release. I feel it is getting better (a lot)
## TL;DR: Like JSX, but for SSR HTML + HTMX.
## Features
- **SSR-only** - No client runtime. Outputs plain HTML.
- **HTMX as HTML** - Write `get`, `post`, `target`, `swap` as native attributes
- **Type-safe routes** - Branded `Route` types with automatic parameter
inference
- **Co-located components** - `hsxComponent()` bundles route + handler + render
- **Page guardrails** - `hsxPage()` enforces semantic, style-free layouts
- **Branded IDs** - `id("name")` returns `Id<"name">` typed as `"#name"`
- **Auto HTMX injection** - `` injected when
needed
- **No manual hx-\*** - Throws at render time if you write `hx-get` directly
- **Widgets** - Define once, serve via SSR or embed as iframes with
Declarative Shadow DOM
## Installation
### From JSR
```bash
deno add jsr:@srdjan/hsx
```
Or import directly:
```ts
import { id, render, route } from "jsr:@srdjan/hsx";
```
### Separate Packages
HSX is a monorepo with three packages:
```ts
// Core - JSX rendering, type-safe routes, hsxComponent, hsxPage
import { Fragment, hsxComponent, hsxPage, id, render, route } from "jsr:@srdjan/hsx";
// Styles - ready-to-use CSS with theming support
import { HSX_STYLES_PATH, hsxStyles } from "jsr:@srdjan/hsx-styles";
// Widgets - embeddable widget protocol + SSR/embed adapters
import { widgetToHsxComponent } from "jsr:@srdjan/hsx-widgets/ssr";
```
Install individually:
```bash
deno add jsr:@srdjan/hsx
deno add jsr:@srdjan/hsx-styles
deno add jsr:@srdjan/hsx-widgets
```
### Selective Imports (Tree-Shaking)
For smaller bundles, import only what you need:
```ts
// Core only - render, route, id, Fragment (smaller bundle)
import { render, route, id, Fragment } from "jsr:@srdjan/hsx/core";
// Components only - hsxComponent, hsxPage
import { hsxComponent, hsxPage } from "jsr:@srdjan/hsx/components";
// Everything (default)
import { render, route, hsxComponent, hsxPage } from "jsr:@srdjan/hsx";
```
### From Source
Clone and import using workspace package names:
```ts
import { hsxComponent, hsxPage, id, render, route } from "@srdjan/hsx";
```
## Quick Start (Low-Level API)
> **Note:** This shows the low-level API using `route()`. For most projects, use
> the [hsxComponent pattern](#hsx-component-pattern-recommended) below instead.
```tsx
import { id, render, route } from "@srdjan/hsx";
const routes = {
todos: route("/todos", () => "/todos"),
};
const ids = {
list: id("todo-list"),
};
function Page() {
return (
<html>
<head>
<title>HSX Demo</title>
</head>
<body>
<form post={routes.todos} target={ids.list} swap="outerHTML">
<input name="text" required />
<button type="submit">Add</button>
</form>
<ul id="todo-list">{/* items */}</ul>
</body>
</html>
);
}
Deno.serve(() => render(<Page />));
```
**Output HTML:**
```html
<html>
<head><title>HSX Demo</title></head>
<body>
<form hx-post="/todos" hx-target="#todo-list" hx-swap="outerHTML">
<input name="text" required>
<button type="submit">Add</button>
</form>
<ul id="todo-list"></ul>
<script src="/static/htmx.js">
`
- Semantic tags (header/main/section/article/h1-h6/p/ul/ol/li/etc.) have **no**
`class` or inline `style`
- `` tags live in `<head>`; CSS belongs there, not inline
- Composition stays within semantic HTML + HSX components
```tsx
import { hsxComponent, hsxPage } from "jsr:@srdjan/hsx";
const Widget = hsxComponent("/data", {
handler: () => ({ message: "Hi" }),
render: ({ message }) => <p>{message}</p>,
});
const Page = hsxPage(() => (
<html lang="en">
<head>
<title>Guarded Page</title>
<style>{"body { font-family: system-ui; }"}
Welcome