{"id":28217517,"url":"https://github.com/ije/mono-jsx","last_synced_at":"2026-04-01T19:04:04.190Z","repository":{"id":290049072,"uuid":"844722507","full_name":"ije/mono-jsx","owner":"ije","description":"`\u003chtml\u003e` as a `Response`.","archived":false,"fork":false,"pushed_at":"2026-03-25T06:02:56.000Z","size":1073,"stargazers_count":530,"open_issues_count":4,"forks_count":9,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-03-25T17:36:07.125Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ije.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-08-19T20:56:00.000Z","updated_at":"2026-03-25T05:28:55.000Z","dependencies_parsed_at":"2026-03-24T16:03:30.891Z","dependency_job_id":null,"html_url":"https://github.com/ije/mono-jsx","commit_stats":null,"previous_names":["ije/mono-jsx"],"tags_count":48,"template":false,"template_full_name":null,"purl":"pkg:github/ije/mono-jsx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ije%2Fmono-jsx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ije%2Fmono-jsx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ije%2Fmono-jsx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ije%2Fmono-jsx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ije","download_url":"https://codeload.github.com/ije/mono-jsx/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ije%2Fmono-jsx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31291024,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-18T00:12:43.001Z","updated_at":"2026-04-01T19:04:04.144Z","avatar_url":"https://github.com/ije.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mono-jsx\n\n![`\u003chtml\u003e` as a `Response`](./.github/og-image.png)\n\nmono-jsx is a JSX runtime that renders the `\u003chtml\u003e` element to a `Response` object.\n\n- 🚀 No build step needed\n- 🦋 Lightweight (12KB gzipped), zero dependencies\n- 🚦 Signals as reactive primitives\n- ⚡️ Use web components, no virtual DOM\n- 💡 Complete Web API TypeScript definitions\n- ⏳ Streaming rendering\n- 🗂️ Built-in router (SPA mode)\n- 🔑 Session storage\n- 🥷 [htmx](#using-htmx) integration\n- 🌎 Universal, works in Node.js, Deno, Bun, Cloudflare Workers, etc.\n\nPlayground: https://val.town/x/ije/mono-jsx\n\n## Installation\n\nmono-jsx supports all modern JavaScript runtimes including Node.js, Deno, Bun, and Cloudflare Workers.\nYou can install it via `npm`, `deno`, or `bun`:\n\n```bash\n# Node.js, Cloudflare Workers, or other node-compatible runtimes\nnpm i mono-jsx\n\n# Deno\ndeno add npm:mono-jsx\n\n# Bun\nbun add mono-jsx\n```\n\n### Setup JSX Runtime\n\nTo use mono-jsx as your JSX runtime, add the following configuration to your `tsconfig.json` (or `deno.json` for Deno):\n\n```jsonc\n{\n  \"compilerOptions\": {\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"mono-jsx\"\n  }\n}\n```\n\nYou can also run `mono-jsx setup` to automatically add the configuration to your project:\n\n```bash\n# Node.js, Cloudflare Workers, or other node-compatible runtimes\nnpx mono-jsx setup\n\n# Deno\ndeno run -A npm:mono-jsx setup\n\n# Bun\nbunx mono-jsx setup\n```\n\n### Zero Configuration\n\nAlternatively, you can use the `@jsxImportSource` pragma directive without installing mono-jsx (no package.json/tsconfig/node_modules). The runtime (Deno/Bun) automatically installs mono-jsx to your computer:\n\n```js\n// Deno, Valtown\n/** @jsxImportSource https://esm.sh/mono-jsx */\n\n// Bun\n/** @jsxImportSource mono-jsx */\n```\n\n## Usage\n\nmono-jsx allows you to return an `\u003chtml\u003e` JSX element as a `Response` object in the `fetch` handler:\n\n```tsx\n// app.tsx\n\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml\u003e\n      \u003ch1\u003eWelcome to mono-jsx!\u003c/h1\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\nFor Deno/Bun users, you can run the `app.tsx` directly:\n\n```bash\ndeno serve app.tsx\nbun app.tsx\n```\n\nIf you're building a web app with [Cloudflare Workers](https://developers.cloudflare.com/workers/wrangler/commands/#dev), use `wrangler dev` to start your app in development mode:\n\n```bash\nnpx wrangler dev app.tsx\n```\n\n**Node.js doesn't support JSX syntax or declarative fetch servers**, we recommend using mono-jsx with [srvx](https://srvx.h3.dev) and [tsx](https://tsx.is) (as JSX loader) to start your app:\n\n```bash\n# npm i srvx tsx\nnpx srvx --import tsx app.tsx\n```\n\n\u003e [!NOTE]\n\u003e Only the root `\u003chtml\u003e` element will be rendered as a `Response` object. You cannot return a `\u003cdiv\u003e` or any other element directly from the `fetch` handler. This is a limitation of mono-jsx.\n\n## Using JSX\n\nmono-jsx uses [**JSX**](https://react.dev/learn/describing-the-ui) to describe the user interface, similar to React but with key differences.\n\n### Using Standard HTML Property Names\n\nmono-jsx adopts standard HTML property names, avoiding React's custom naming conventions:\n\n- `className` → `class`\n- `htmlFor` → `for`\n- `onChange` → `onInput`\n\n### Composition with `class`\n\nmono-jsx allows you to compose the `class` property using arrays of strings, objects, or expressions:\n\n```tsx\n\u003cdiv\n  class={[\n    \"container box\",\n    isActive \u0026\u0026 \"active\",\n    { hover: isHover },\n  ]}\n/\u003e;\n```\n\n### Using Pseudo Classes and Media Queries in `style`\n\nmono-jsx supports [pseudo classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes), [pseudo elements](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements), [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries), and [CSS nesting](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting) in the `style` property:\n\n```tsx\n\u003ca\n  style={{\n    display: \"inline-flex\",\n    gap: \"0.5em\",\n    color: \"black\",\n    \"::after\": { content: \"↩️\" },\n    \":hover\": { textDecoration: \"underline\" },\n    \"@media (prefers-color-scheme: dark)\": { color: \"white\" },\n    \"\u0026 .icon\": { width: \"1em\", height: \"1em\" },\n  }}\n\u003e\n  \u003cimg class=\"icon\" src=\"link.png\" /\u003e\n  Link\n\u003c/a\u003e;\n```\n\n### Using View Transition\n\nmono-jsx supports [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) to create smooth transitions between views. To use view transitions, add the `viewTransition` attribute to the following components:\n\n - `\u003cshow viewTransition=\"view-transition-name\"\u003e`\n - `\u003chidden viewTransition=\"view-transition-name\"\u003e`\n - `\u003cswitch viewTransition=\"view-transition-name\"\u003e`\n - `\u003ccomponent viewTransition=\"view-transition-name\"\u003e`\n - `\u003crouter viewTransition=\"view-transition-name\"\u003e`\n\nYou can set custom transition animations by adding [`::view-transition-group`](https://developer.mozilla.org/en-US/docs/Web/CSS/::view-transition-group), [`::view-transition-old`](https://developer.mozilla.org/en-US/docs/Web/CSS/::view-transition-old), and [`::view-transition-new`](https://developer.mozilla.org/en-US/docs/Web/CSS/::view-transition-new) pseudo-elements with your own CSS animations. For example:\n\n```tsx\nfunction App(this: FC\u003c{ show: boolean }\u003e) {\n  return (\n    \u003cdiv\n      style={{\n        \"@keyframes fade-in\": { from: { opacity: 0 }, to: { opacity: 1 } },\n        \"@keyframes fade-out\": { from: { opacity: 1 }, to: { opacity: 0 } },\n        \"::view-transition-group(fade)\": { animationDuration: \"0.3s\" },\n        \"::view-transition-old(fade)\": { animation: \"0.3s ease-in both fade-out\" },\n        \"::view-transition-new(fade)\": { animation: \"0.3s ease-in both fade-in\" },\n      }}\n    \u003e\n      \u003cshow when={this.show} viewTransition=\"fade\"\u003e\n        \u003ch1\u003eHello world!\u003c/h1\u003e\n      \u003c/show\u003e\n      \u003cbutton onClick={() =\u003e this.show = !this.show}\u003eToggle\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\nYou can also set the `viewTransition` attribute a html element which contains signal children.\n\n```tsx\nfunction App(this: FC\u003c{ message: string }\u003e) {\n  this.message = \"Hello world!\";\n  return (\n    \u003ch1 viewTransition=\"fade\"\u003e{this.message}\u003c/h1\u003e\n  )\n}\n```\n\nYou can also set the view transition name in the style property with the `viewTransition` attribute set to `true`.\n\n```tsx\nfunction App(this: FC\u003c{ message: string }\u003e) {\n  this.message = \"Hello world!\";\n  return (\n    \u003ch1 viewTransition style={{ viewTransitionName: \"fade\" }}\u003e{this.message}\u003c/h1\u003e\n  )\n}\n```\n\n### Using `\u003cslot\u003e` Element\n\nmono-jsx uses [`\u003cslot\u003e`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) elements to render slotted content (equivalent to React's `children` property). You can also add the `name` attribute to define named slots:\n\n```tsx\nfunction Container() {\n  return (\n    \u003cdiv class=\"container\"\u003e\n      {/* Default slot */}\n      \u003cslot /\u003e\n      {/* Named slot */}\n      \u003cslot name=\"desc\" /\u003e\n    \u003c/div\u003e\n  )\n}\n\nfunction App() {\n  return (\n    \u003cContainer\u003e\n      {/* This goes to the named slot */}\n      \u003cp slot=\"desc\"\u003eThis is a description.\u003c/p\u003e\n      {/* This goes to the default slot */}\n      \u003ch1\u003eHello world!\u003c/h1\u003e\n    \u003c/Container\u003e\n  )\n}\n```\n\n### Using `html` Tag Function\n\nmono-jsx provides an `html` tag function to render raw HTML, which is similar to React's `dangerouslySetInnerHTML`.\n\n```tsx\nfunction App() {\n  return \u003cdiv\u003e{html`\u003ch1\u003eHello world!\u003c/h1\u003e`}\u003c/div\u003e;\n}\n```\n\nVariables in the `html` template literal are escaped. To render raw HTML without escaping, call the `html` function with a string literal.\n\n```tsx\nfunction App() {\n  return \u003cdiv\u003e{html(`${\u003ch1\u003eHello world!\u003c/h1\u003e}`)}\u003c/div\u003e;\n}\n```\n\nThe `html` function is globally available without importing. You can also use `css` and `js` functions for CSS and JavaScript:\n\n```tsx\nfunction App() {\n  return (\n    \u003chead\u003e\n      \u003cstyle\u003e{css`h1 { font-size: 3rem; }`}\u003c/style\u003e\n      \u003cscript\u003e{js`console.log(\"Hello world!\")`}\u003c/script\u003e\n    \u003c/head\u003e\n  )\n}\n```\n\n\u003e [!WARNING]\n\u003e The `html` tag function is **unsafe** and can cause [**XSS**](https://en.wikipedia.org/wiki/Cross-site_scripting) vulnerabilities.\n\n### Event Handlers\n\nmono-jsx lets you write event handlers directly in JSX, similar to React:\n\n```tsx\nfunction Button() {\n  return (\n    \u003cbutton onClick={(evt) =\u003e alert(\"BOOM!\")}\u003e\n      Click Me\n    \u003c/button\u003e\n  )\n}\n```\n\nEvent handlers are never called on the server-side. They're serialized to strings and sent to the client. **This means you should NOT use server-side variables or functions in event handlers.**\n\n```tsx\nimport { doSomething } from \"some-library\";\n\nfunction Button(this: FC\u003c{ count: 0 }\u003e, props: { role: string }) {\n  const message = \"BOOM!\";        // server-side variable\n  this.count = 0;                 // initialize a signal\n  console.log(message);           // only prints on server-side\n  return (\n    \u003cbutton\n      role={props.role}\n      onClick={(evt) =\u003e {\n        alert(message);           // ❌ `message` is a server-side variable\n        console.log(props.role);  // ❌ `props` is a server-side variable\n        doSomething();            // ❌ `doSomething` is imported on the server-side\n        Deno.exit(0);             // ❌ `Deno` is unavailable in the browser\n        document.title = \"BOOM!\"; // ✅ `document` is a browser API\n        console.log(evt.target);  // ✅ `evt` is the event object\n        this.count++;             // ✅ update the `count` signal\n      }}\n    \u003e\n      \u003cslot /\u003e\n    \u003c/button\u003e\n  )\n}\n```\n\nmono-jsx allows you to use a function as the value of the `action` attribute of the `\u003cform\u003e` element. The function will be called on form submission, and the `FormData` object will contain the form data.\n\n```tsx\nfunction App() {\n  return (\n    \u003cform action={(data: FormData) =\u003e console.log(data.get(\"name\"))}\u003e\n      \u003cinput type=\"text\" name=\"name\" /\u003e\n      \u003cbutton type=\"submit\"\u003eSubmit\u003c/button\u003e\n    \u003c/form\u003e\n  )\n}\n```\n\n## Async Components\n\nmono-jsx supports async components that return a `Promise` or an async function. With streaming rendering, async components are rendered asynchronously, allowing you to fetch data or perform other async operations before rendering the component.\n\n```tsx\nasync function Loader(props: { url: string }) {\n  const data = await fetch(props.url).then((res) =\u003e res.json());\n  return \u003cJsonViewer data={data} /\u003e;\n}\n\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml\u003e\n      \u003cLoader url=\"https://api.example.com/data\" pending={\u003cp\u003eLoading...\u003c/p\u003e} /\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\nYou can also use async generators to yield multiple elements over time. This is useful for streaming rendering of LLM tokens:\n\n```tsx\nasync function* Chat(props: { prompt: string }) {\n  const stream = await openai.chat.completions.create({\n    model: \"gpt-4\",\n    messages: [{ role: \"user\", content: prompt }],\n    stream: true,\n  });\n\n  for await (const event of stream) {\n    const text = event.choices[0]?.delta.content;\n    if (text) {\n      yield \u003cspan\u003e{text}\u003c/span\u003e;\n    }\n  }\n}\n\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml\u003e\n      \u003cChat prompt=\"Tell me a story\" pending={\u003cspan style=\"color:grey\"\u003e●\u003c/span\u003e} /\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\nYou can use `pending` to display a loading state while waiting for async components to render:\n\n```tsx\nasync function Sleep({ ms }) {\n  await new Promise((resolve) =\u003e setTimeout(resolve, ms));\n  return \u003cslot /\u003e;\n}\n\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml\u003e\n      \u003cSleep ms={1000} pending={\u003cp\u003eLoading...\u003c/p\u003e}\u003e\n        \u003cp\u003eAfter 1 second\u003c/p\u003e\n      \u003c/Sleep\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\nYou can set the `rendering` attribute to `\"eager\"` to force synchronous rendering (the `pending` property will be ignored):\n\n```tsx\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml\u003e\n      \u003cSleep ms={1000} rendering=\"eager\"\u003e\n        \u003cp\u003eAfter 1 second\u003c/p\u003e\n      \u003c/Sleep\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\nYou can add the `catch` attribute to handle errors in the async component. The `catch` attribute should be a function that returns a JSX element:\n\n```tsx\nasync function Hello() {\n  throw new Error(\"Something went wrong!\");\n  return \u003cp\u003eHello world!\u003c/p\u003e;\n}\n\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml\u003e\n      \u003cHello catch={err =\u003e \u003cp\u003e{err.message}\u003c/p\u003e} /\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n## Lazy Rendering\n\nmono-jsx renders HTML on the server side and sends no hydration JavaScript to the client. To render a component dynamically on the client, you can use the `\u003ccomponent\u003e` element to ask the server to render a component:\n\nTo render a component by name, you can use the `\u003ccomponent\u003e` element with the `name` prop, and ensure the component is registered in the `components` prop of root `\u003chtml\u003e` element.\n\n```tsx\nfunction Foo(props: { bar: string }) {\n  return \u003ch1\u003e{props.bar}\u003c/h1\u003e;\n}\n\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml request={req} components={{ Foo }}\u003e\n      \u003ccomponent name=\"Foo\" props={{ bar: \"baz\" }} pending={\u003cp\u003eLoading...\u003c/p\u003e} /\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\nYou can use the `\u003ccomponent\u003e` element with the `is` prop to render a component by function reference without registering the component in the `components` prop of root `\u003chtml\u003e` element.\n\n```tsx\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml request={req}\u003e\n      \u003ccomponent is={Foo} props={{ bar: \"baz\" }} pending={\u003cp\u003eLoading...\u003c/p\u003e} /\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\nOr you can use the `\u003ccomponent\u003e` element with the `as` prop to render a component by JSX element.\n\n```tsx\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml request={req}\u003e\n      \u003ccomponent as={\u003cFoo bar=\"baz\" /\u003e} pending={\u003cp\u003eLoading...\u003c/p\u003e} /\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\nYou can also use [signals](#using-signals) for `name` or `props` attributes of a component. Changing the signal value will trigger the component to re-render with the new name or props:\n\n```tsx\nimport { Profile, Projects, Settings } from \"./pages.tsx\"\n\nfunction Dash(this: FC\u003c{ page: \"Profile\" | \"Projects\" | \"Settings\" }\u003e) {\n  this.page = \"Profile\";\n\n  return (\n    \u003c\u003e\n      \u003cdiv class=\"tab\"\u003e\n        \u003cbutton onClick={() =\u003e this.page = \"Profile\"}\u003eProfile\u003c/button\u003e\n        \u003cbutton onClick={() =\u003e this.page = \"Projects\"}\u003eProjects\u003c/button\u003e\n        \u003cbutton onClick={() =\u003e this.page = \"Settings\"}\u003eSettings\u003c/button\u003e\n      \u003c/div\u003e\n      \u003cdiv class=\"page\"\u003e\n        \u003ccomponent name={this.page} pending={\u003cp\u003eLoading...\u003c/p\u003e} /\u003e\n      \u003c/div\u003e\n    \u003c/\u003e\n  )\n}\n\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml request={req} components={{ Profile, Projects, Settings }}\u003e\n      \u003cDash /\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\nYou can use the `\u003cshow\u003e` element to control when to render a component:\n\n```tsx\nasync function Lazy(this: FC\u003c{ show: boolean }\u003e, props: { url: string }) {\n  return (\n    \u003cdiv\u003e\n      \u003cshow when={this.show}\u003e\n        \u003ccomponent name=\"Foo\" props={{ bar: \"baz\" }} pending={\u003cp\u003eLoading...\u003c/p\u003e} /\u003e\n      \u003c/show\u003e\n     \u003cbutton onClick={() =\u003e this.show = true }\u003eLoad `Foo` Component\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml request={req} components={{ Foo }}\u003e\n      \u003cLazy /\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n## Using Signals\n\nmono-jsx uses signals for updating the view when a signal changes. Signals are similar to React's state, but they are more lightweight and efficient. You can use signals to manage state in your components.\n\n### Using Component Signals\n\nYou can use the `this` keyword in your components to manage signals. Signals are bound to the component instance, can be updated directly, and the view will automatically re-render when a signal changes:\n\n```tsx\nfunction Counter(this: FC\u003c{ count: number }\u003e, props: { initialCount?: number }) {\n  // Initialize a signal\n  this.count = props.initialCount ?? 0;\n\n  // or you can use `this.init` to initialize the signals\n  this.init({ count: props.initialCount ?? 0 });\n\n  return (\n    \u003cdiv\u003e\n      {/* render signal */}\n      \u003cspan\u003e{this.count}\u003c/span\u003e\n\n      {/* Update signal to trigger re-render */}\n      \u003cbutton onClick={() =\u003e this.count--}\u003e-\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e this.count++}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n### Using App Signals\n\nYou can define app signals by adding the `app` prop to the root `\u003chtml\u003e` element. The app signals are available in all components via `this.app.\u003cSignalName\u003e`. Changes to the app signals will trigger re-renders in all components that use them:\n\n```tsx\ninterface IAppSignals {\n  themeColor: string;\n}\n\nfunction Header(this: WithAppSignals\u003cFC, IAppSignals\u003e) {\n  return (\n    \u003cheader\u003e\n      \u003ch1 style={{ color: this.app.themeColor }}\u003eWelcome to mono-jsx!\u003c/h1\u003e\n    \u003c/header\u003e\n  )\n}\n\nfunction Footer(this: WithAppSignals\u003cFC, IAppSignals\u003e) {\n  return (\n    \u003cfooter\u003e\n      \u003cp style={{ color: this.app.themeColor }}\u003e(c) 2025 mono-jsx.\u003c/p\u003e\n    \u003c/footer\u003e\n  )\n}\n\nfunction Main(this: WithAppSignals\u003cFC, IAppSignals\u003e) {\n  return (\n    \u003cmain\u003e\n      \u003cp\u003e\n        \u003clabel\u003eTheme Color: \u003c/label\u003e\n        \u003cinput type=\"color\" $value={this.app.themeColor} /\u003e\n      \u003c/p\u003e\n    \u003c/main\u003e\n  )\n}\n\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml app={{ themeColor: \"#232323\" }}\u003e\n      \u003cHeader /\u003e\n      \u003cMain /\u003e\n      \u003cFooter /\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n### Using Computed Signals\n\nYou can use `this.computed` to create a derived signal based on other signals:\n\n```tsx\nfunction App(this: FC\u003c{ input: string }\u003e) {\n  this.input = \"Welcome to mono-jsx\";\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003e{this.computed(() =\u003e this.input + \"!\")}\u003c/h1\u003e\n      \u003cinput type=\"text\" $value={this.input} /\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n\u003e [!TIP]\n\u003e You can use `this.$` as a shorthand for `this.computed` to create computed signals.\n\n### Using Effects\n\nYou can use `this.effect` to create side effects based on signals. The effect will run whenever the signal changes:\n\n```tsx\nfunction App(this: FC\u003c{ count: number }\u003e) {\n  this.count = 0;\n\n  this.effect(() =\u003e {\n    console.log(\"Count changed:\", this.count);\n  });\n\n  return (\n    \u003cdiv\u003e\n      \u003cspan\u003e{this.count}\u003c/span\u003e\n      \u003cbutton onClick={() =\u003e this.count++}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\nThe callback function of `this.effect` can return a cleanup function that gets run once the component element has been removed via `\u003cshow\u003e`, `\u003chidden\u003e` or `\u003cswitch\u003e` conditional rendering:\n\n```tsx\nfunction Counter(this: FC\u003c{ count: number }\u003e) {\n  this.count = 0;\n\n  this.effect(() =\u003e {\n    const interval = setInterval(() =\u003e {\n      this.count++;\n    }, 1000);\n\n    return () =\u003e clearInterval(interval);\n  });\n\n  return (\n    \u003cdiv\u003e\n      \u003cspan\u003e{this.count}\u003c/span\u003e\n    \u003c/div\u003e\n  )\n}\n\nfunction App(this: FC\u003c{ show: boolean }\u003e) {\n  return (\n    \u003cdiv\u003e\n      \u003cshow when={this.show}\u003e\n        \u003cCounter /\u003e\n      \u003c/show\u003e\n      \u003cbutton onClick={e =\u003e this.show = !this.show }\u003e{this.computed(() =\u003e this.show ? 'Hide': 'Show')}\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n### Using `\u003cshow\u003e` Element with Signals\n\nThe `\u003cshow\u003e` element conditionally renders content based on the `when` prop. You can use signals to control the visibility of the content on the client side.\n\n```tsx\nfunction App(this: FC\u003c{ show: boolean }\u003e) {\n   const toggle = () =\u003e {\n    this.show = !this.show;\n  }\n\n  return (\n    \u003cdiv\u003e\n      \u003cshow when={this.show}\u003e\n        \u003ch1\u003eWelcome to mono-jsx!\u003c/h1\u003e\n      \u003c/show\u003e\n\n      \u003cbutton onClick={toggle}\u003e\n        {this.$(() =\u003e this.show ? \"Hide\" : \"Show\")}\n      \u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\nmono-jsx also provides a `\u003chidden\u003e` element that is similar to `\u003cshow\u003e`, but it conditionally hides the content based on the `when` prop.\n\n```tsx\nfunction App(this: FC\u003c{ hidden: boolean }\u003e) {\n  return (\n    \u003cdiv\u003e\n      \u003chidden when={this.hidden}\u003e\n        \u003ch1\u003eWelcome to mono-jsx!\u003c/h1\u003e\n      \u003c/hidden\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\nIf you need `if-else` logic in JSX, use `\u003cswitch\u003e` element instead:\n\n```tsx\nfunction App(this: FC\u003c{ ok: boolean }\u003e) {\n  return (\n    \u003cdiv\u003e\n      \u003cswitch value={this.ok}\u003e\n        \u003cspan slot=\"true\"\u003eTrue\u003c/span\u003e\n        \u003cspan slot=\"false\"\u003eFalse\u003c/span\u003e\n      \u003c/switch\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n### Using `\u003cswitch\u003e` Element with Signals\n\nThe `\u003cswitch\u003e` element renders different content based on the `value` prop. Elements with matching `slot` attributes are displayed when their value matches, otherwise default slots are shown. Like `\u003cshow\u003e`, you can use signals to control the value on the client side.\n\n```tsx\nfunction App(this: FC\u003c{ lang: \"en\" | \"zh\" | \"🙂\" }\u003e) {\n  this.lang = \"en\";\n\n  return (\n    \u003cdiv\u003e\n      \u003cswitch value={this.lang}\u003e\n        \u003ch1 slot=\"en\"\u003eHello, world!\u003c/h1\u003e\n        \u003ch1 slot=\"zh\"\u003e你好，世界！\u003c/h1\u003e\n        \u003ch1 slot=\"🙂\"\u003e✋🌎❗️\u003c/h1\u003e\n      \u003c/switch\u003e\n      \u003cp\u003e\n        \u003cbutton onClick={() =\u003e this.lang = \"en\"}\u003eEnglish\u003c/button\u003e\n        \u003cbutton onClick={() =\u003e this.lang = \"zh\"}\u003e中文\u003c/button\u003e\n        \u003cbutton onClick={() =\u003e this.lang = \"🙂\"}\u003e🙂\u003c/button\u003e\n      \u003c/p\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n### Form Input Two-way Binding\n\nYou can use the `$value` attribute to bind a signal to the value of a form input element. The `$value` attribute is a two-way data binding, which means that when the input value changes, the signal will be updated, and when the signal changes, the input value will be updated.\n\n```tsx\nfunction App(this: FC\u003c{ value: string }\u003e) {\n  this.value = \"Welcome to mono-jsx\";\n  this.effect(() =\u003e {\n    console.log(\"value changed:\", this.value);\n  });\n  // return \u003cinput value={this.value} oninput={e =\u003e this.value = e.target.value} /\u003e;\n  return \u003cinput $value={this.value} /\u003e;\n}\n```\n\nYou can also use the `$checked` attribute to bind a signal to the checked state of a checkbox or radio input element.\n\n```tsx\nfunction App(this: FC\u003c{ checked: boolean }\u003e) {\n  this.effect(() =\u003e {\n    console.log(\"checked changed:\", this.checked);\n  });\n  // return \u003cinput type=\"checkbox\" checked={this.checked} onchange={e =\u003e this.checked = e.target.checked} /\u003e;\n  return \u003cinput type=\"checkbox\" $checked={this.checked} /\u003e;\n}\n```\n\n### Limitations of Signals\n\n1\\. Arrow functions are non-stateful components.\n\n```tsx\n// ❌ Won't work - uses `this` in a non-stateful component\nconst App = () =\u003e {\n  this.count = 0;\n  return (\n    \u003cdiv\u003e\n      \u003cspan\u003e{this.count}\u003c/span\u003e\n      \u003cbutton onClick={() =\u003e this.count++}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  )\n};\n\n// ✅ Works correctly\nfunction App(this: FC) {\n  this.count = 0;\n  return (\n    \u003cdiv\u003e\n      \u003cspan\u003e{this.count}\u003c/span\u003e\n      \u003cbutton onClick={() =\u003e this.count++}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n2\\. Signals cannot be computed outside of the `this.computed` method.\n\n```tsx\n// ❌ Won't work - updates of a signal won't refresh the view\nfunction App(this: FC\u003c{ message: string }\u003e) {\n  this.message = \"Welcome to mono-jsx\";\n  return (\n    \u003cdiv\u003e\n      \u003ch1 title={this.message + \"!\"}\u003e{this.message + \"!\"}\u003c/h1\u003e\n      \u003cbutton onClick={() =\u003e this.message = \"Clicked\"}\u003e\n        Click Me\n      \u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\n// ✅ Works correctly\nfunction App(this: FC) {\n  this.message = \"Welcome to mono-jsx\";\n  return (\n    \u003cdiv\u003e\n      \u003ch1 title={this.$(() =\u003e this.message + \"!\")}\u003e{this.$(() =\u003e this.message + \"!\")}\u003c/h1\u003e\n      \u003cbutton onClick={() =\u003e this.message = \"Clicked\"}\u003e\n        Click Me\n      \u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n3\\. The callback function of `this.computed` must be a pure function. This means it should not create side effects or access any non-stateful variables. For example, you cannot use `Deno` or `document` in the callback function:\n\n```tsx\n// ❌ Won't work - throws `Deno is not defined` when the button is clicked\nfunction App(this: FC\u003c{ message: string }\u003e) {\n  this.message = \"Welcome to mono-jsx\";\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003e{this.computed(() =\u003e this.message + \"! (Deno \" + Deno.version.deno + \")\")}\u003c/h1\u003e\n      \u003cbutton onClick={() =\u003e this.message = \"Clicked\"}\u003e\n        Click Me\n      \u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\n// ✅ Works correctly\nfunction App(this: FC\u003c{ message: string, denoVersion: string }\u003e) {\n  this.denoVersion = Deno.version.deno;\n  this.message = \"Welcome to mono-jsx\";\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003e{this.computed(() =\u003e this.message + \"! (Deno \" + this.denoVersion + \")\")}\u003c/h1\u003e\n      \u003cbutton onClick={() =\u003e this.message = \"Clicked\"}\u003e\n        Click Me\n      \u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n## Using `this` in Components\n\nmono-jsx binds a scoped signals object to `this` of your component functions. This allows you to access signals, context, and request information directly in your components.\n\nThe `this` object has the following built-in properties:\n\n- `app`: The app global signals.\n- `context`: The context object defined on the root `\u003chtml\u003e` element.\n- `request`: The request object from the `fetch` handler.\n- `session`: The session storage.\n- `refs`: A map of refs defined in the component.\n- `computed(fn)`: A method to create a computed signal.\n- `$(fn)`:  A shortcut for `computed(fn)`.\n- `effect(fn)`: A method to create side effects.\n\n```ts\ntype FC\u003cSignals = {}, Refs = {}\u003e = {\n  readonly app: AppSignals \u0026 { refs: AppRefs; url: WithParams\u003cURL\u003e }\n  readonly context: Context;\n  readonly request: WithParams\u003cRequest\u003e;\n  readonly session: Session;\n  readonly refs: Refs;\n  readonly computed: \u003cT = unknown\u003e(fn: () =\u003e T) =\u003e T;\n  readonly $: FC[\"computed\"]; // A shortcut for `FC.computed`.\n  readonly effect: (fn: () =\u003e void | (() =\u003e void)) =\u003e void;\n} \u0026 Signals;\n\n// define `AppSignals` type\nfunction Component(this: WithAppSignals\u003cFC, { title: string }\u003e) {\n  this.app.title // type: 'string'\n}\n\n// define `Context` type\nfunction Component(this: WithContext\u003cFC, { secret: string }\u003e) {\n  this.context.secret // type: 'string'\n}\n```\n\n### Using Signals\n\nSee the [Using Signals](#using-signals) section for more details on how to use signals in your components.\n\n### Using Refs\n\nYou can use `this.refs` to access refs in your components. Refs are defined using the `ref` attribute in JSX, and they allow you to access DOM elements directly. The `refs` object is a map of ref names to DOM elements.\n\n```tsx\nfunction App(this: WithRefs\u003cFC, { input?: HTMLInputElement }\u003e) {\n  this.effect(() =\u003e {\n    this.refs.input?.addEventListener(\"input\", (evt) =\u003e {\n      console.log(\"Input changed:\", evt.target.value);\n    });\n  });\n\n  return (\n    \u003cdiv\u003e\n      \u003cinput ref={this.refs.input} type=\"text\" /\u003e\n      \u003cbutton onClick={() =\u003e this.refs.input?.focus()}\u003eFocus\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\nYou can also use `this.app.refs` to access app-level refs:\n\n```tsx\nfunction Layout(this: FC) {\n  return (\n    \u003c\u003e\n    \u003cheader\u003e\n      \u003ch1 ref={this.refs.h1}\u003eWelcome to mono-jsx!\u003c/h1\u003e\n    \u003c/header\u003e\n    \u003cmain\u003e\n      \u003cslot /\u003e\n    \u003c/main\u003e\n    \u003c/\u003e\n  )\n}\n```\n\nThe `\u003ccomponent\u003e` element also supports the `ref` attribute, which allows you to control the component rendering manually. The `ref` will be a `ComponentElement` that has the `name`, `props`, and `refresh` properties:\n\n- `name`: The name of the component to render.\n- `props`: The props to pass to the component.\n- `refresh`: A method to re-render the component with the current name and props.\n\n```tsx\nimport type { ComponentElement } from \"mono-jsx\";\n\nfunction App(this: WithRefs\u003cFC, { component: ComponentElement }\u003e) {\n  this.effect(() =\u003e {\n    // updating the component name and props will trigger a re-render of the component\n    this.refs.component.name = \"Foo\";\n    this.refs.component.props = {};\n\n    const timer = setInterval(() =\u003e {\n      // re-render the component\n      this.refs.component.refresh();\n    }, 1000);\n    return () =\u003e clearInterval(timer); // cleanup\n  });\n  return (\n    \u003cdiv\u003e\n      \u003ccomponent ref={this.refs.component} /\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n### Using Context\n\nYou can use the `context` property in `this` to access context values in your components. The context is defined on the root `\u003chtml\u003e` element:\n\n```tsx\nfunction Dash(this: WithContext\u003cFC, { auth: { uuid: string; name: string } }\u003e) {\n  const { auth } = this.context;\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003eWelcome back, {auth.name}!\u003c/h1\u003e\n      \u003cp\u003eYour UUID is {auth.uuid}\u003c/p\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default {\n  fetch: async (req) =\u003e {\n    const auth = await doAuth(req);\n    return (\n      \u003chtml context={{ auth }} request={req}\u003e\n        {!auth \u0026\u0026 \u003cp\u003ePlease Login\u003c/p\u003e}\n        {auth \u0026\u0026 \u003cDash /\u003e}\n      \u003c/html\u003e\n    )\n  }\n}\n```\n\n### Accessing Request Info\n\nYou can access request information in components via the `request` property in `this` which is set on the root `\u003chtml\u003e` element:\n\n```tsx\nfunction RequestInfo(this: FC) {\n  const { request } = this;\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003eRequest Info\u003c/h1\u003e\n      \u003cp\u003e{request.method}\u003c/p\u003e\n      \u003cp\u003e{request.url}\u003c/p\u003e\n      \u003cp\u003e{request.headers.get(\"user-agent\")}\u003c/p\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml request={req}\u003e\n      \u003cRequestInfo /\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n## Using Router (SPA mode)\n\nmono-jsx provides a built-in `\u003crouter\u003e` element that allows your app to render components based on the current URL. On the client side, it listens to all `click` events on `\u003ca\u003e` elements and asynchronously fetches the route component without reloading the entire page.\n\nTo use the router, you need to define your routes as a mapping of URL patterns to components and pass it to the `\u003chtml\u003e` element as the `routes` prop. The `request` prop is also required to match the current URL against the defined routes.\n\n```tsx\nconst routes = {\n  \"/\": Home,\n  \"/about\": About,\n  \"/blog\": Blog,\n  \"/post/:id\": Post,\n}\n\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml request={req} routes={routes}\u003e\n      \u003cnav\u003e\n        \u003ca href=\"/\"\u003eHome\u003c/a\u003e\n        \u003ca href=\"/about\"\u003eAbout\u003c/a\u003e\n        \u003ca href=\"/blog\"\u003eBlog\u003c/a\u003e\n      \u003c/nav\u003e\n      \u003crouter /\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\nThe mono-jsx router requires [URLPattern](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) to match routes:\n\n- ✅ Deno\n- ✅ Cloudflare Workers\n- ✅ Node.js (\u003e= 24)\n\nFor Bun users, mono-jsx provides a `buildRoutes` function that uses Bun's built-in server routing:\n\n```tsx\nimport { buildRoutes } from \"mono-jsx\"\n\nconst routes = {\n  \"/\": Home,\n  \"/about\": About,\n  \"/blog\": Blog,\n  \"/post/:id\": Post,\n}\n\nBun.serve({\n  routes: buildRoutes((req) =\u003e (\n    \u003chtml request={req} routes={routes}\u003e\n      \u003cnav\u003e\n        \u003ca href=\"/\"\u003eHome\u003c/a\u003e\n        \u003ca href=\"/about\"\u003eAbout\u003c/a\u003e\n        \u003ca href=\"/blog\"\u003eBlog\u003c/a\u003e\n      \u003c/nav\u003e\n      \u003crouter /\u003e\n    \u003c/html\u003e\n  ))\n})\n```\n\n### Using Route `params`\n\nWhen you define a route with a parameter (e.g., `/post/:id`), mono-jsx will automatically extract the parameter from the URL and make it available in the route component. The `params` object is available in the `request` property of the component's `this` context.\n\nYou can access the `params` object in your route components to get the values of the parameters defined in the route pattern:\n\n\n```tsx\n// router pattern: \"/post/:id\"\nfunction Post(this: FC) {\n  this.request.url         // \"http://localhost:3000/post/123\"\n  this.request.params?.id  // \"123\"\n}\n```\n\n### Using Route Form\n\nmono-jsx allows you to define a `FormHandler` function for route components to handle form data from form submissions on the current route page on the client side. To submit the form data to the `FormHandler` function, you need to set the `route` attribute on the `\u003cform\u003e` element.\n\nmono-jsx provides two built-in elements to allow you to control the post-submit behavior:\n\n- `\u003cinvalid for=\"...\"\u003e{message}\u003c/invalid\u003e` to set custom validation state for the form elements.\n- `\u003credirect to=\"...\" /\u003e` to redirect to a new route/URL.\n\n```tsx\nasync function Login(this: FC) {\n  return (\n    \u003cform route style={{ \"\u0026 input:invalid\": { borderColor: \"red\" } }}\u003e\n      \u003cinput type=\"text\" name=\"username\" placeholder=\"Username\" /\u003e\n      \u003cinput type=\"password\" name=\"password\" placeholder=\"Password\" /\u003e\n      \u003cbutton type=\"submit\"\u003eLogin\u003c/button\u003e\n    \u003c/form\u003e\n  )\n}\n\n// `FormHandler` function will be called when the form in '/login' route is submitted.\nLogin.FormHandler = function(this: FC, data: FormData) {\n  const user = await auth(data)\n  if (!user) {\n    return \u003cinvalid for=\"username,password\"\u003eInvalid Username/Password\u003c/invalid\u003e\n  }\n  this.session.set(\"user\", user)\n  return \u003credirect to=\"/dash\" /\u003e\n}\n\nconst routes = {\n  \"/login\": Login,\n  // ... other routes ...\n}\n```\n\n\u003e [!NOTE]\n\u003e You can use `:invalid` CSS selector to style the form elements with invalid state.\n\nYou can also return regular HTML elements from the route form post response. The `formslot` element is used to\nmark the position where the returned HTML elements will be inserted.\n\n- `\u003cformslot mode=\"replaceChildren\" /\u003e`: Replace the `formslot` element's children with the HTML. This is the default mode.\n- `\u003cformslot mode=\"insertafter\" /\u003e`: Insert HTML after the `formslot` element.\n- `\u003cformslot mode=\"insertbefore\" /\u003e`: Insert HTML before the `formslot` element.\n\n```tsx\nfunction MyRoute(this: FC) {\n  return (\n    \u003cform route\u003e\n      {/* \u003c- new message will be inserted here */}\n      \u003cformslot mode=\"insertbefore\" /\u003e\n      \u003cinput type=\"text\" name=\"message\" placeholder=\"Type Message...\" style={{ \":invalid\": { borderColor: \"red\" } }} /\u003e\n      \u003cbutton type=\"submit\"\u003eSend\u003c/button\u003e\n    \u003c/form\u003e\n  )\n}\n\nMyRoute.FormHandler = function(this: FC, data: FormData) {\n  const message = data.get(\"message\") as string | null;\n  if (!message) {\n    return \u003cinvalid for=\"message\"\u003eMessage is required\u003c/invalid\u003e\n  }\n  return \u003cp\u003e{message}\u003c/p\u003e\n}\n```\n\nYou can also use the `name` attribute to specify the name of the formslot element. And you can use the `formslot` attribute to specify the name of the slot to insert the HTML into.\n\n```tsx\nfunction MyRoute(this: FC) {\n  return (\n    \u003cdiv\u003e\n      \u003cformslot name=\"message\" /\u003e\n      \u003cform route\u003e\n        \u003cbutton type=\"submit\"\u003eSend\u003c/button\u003e\n      \u003c/form\u003e\n    \u003c/div\u003e\n  )\n}\n\nMyRoute.FormHandler = function(this: FC, data: FormData) {\n  return \u003cp formslot=\"message\"\u003eHello, world!\u003c/p\u003e\n}\n```\n\n`formslot` element accepts the `onUpdate` attribute to set a callback function that will be called when the formslot element is updated.\n\n```tsx\nfunction MyRoute(this: FC) {\n  return (\n    \u003cform\u003e\n      \u003cinput type=\"text\" name=\"message\" placeholder=\"Type Message...\" /\u003e\n      \u003cbutton type=\"submit\"\u003eSend\u003c/button\u003e\n      \u003cformslot onUpdate={(evt) =\u003e console.log(\"message updated:\", evt.target.textContent)} /\u003e\n    \u003c/form\u003e\n  )\n}\n\nMyRoute.FormHandler = function(this: FC, data: FormData) {\n  return \u003cp\u003e{data.get(\"message\")}\u003c/p\u003e\n}\n```\n\nThe `hidden` attribute can be used to hide the formslot payload from the form handler.\n\n```tsx\n\u003cformslot onUpdate={(evt) =\u003e console.log(\"message updated:\", evt.target.textContent)} hidden /\u003e\n```\n\n### Using `this.app.url` Signal\n\n`this.app.url` is an app-level signal that contains the current route URL and parameters. The `this.app.url` signal is automatically updated when the route changes, so you can use it to display the current URL in your components or control the view with `\u003cshow\u003e`, `\u003chidden\u003e` or `\u003cswitch\u003e` elements:\n\n```tsx\nfunction App(this: FC) {\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003eCurrent Pathname: {this.$(() =\u003e this.app.url.pathname)}\u003c/h1\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n### Navigation between Pages\n\nTo navigate between pages, you can use `\u003ca\u003e` elements with `href` attributes that match the defined routes. The router will intercept the click events of these links and fetch the corresponding route component without reloading the page:\n\n```tsx\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml request={req} routes={routes}\u003e\n      \u003cnav\u003e\n        \u003ca href=\"/\"\u003eHome\u003c/a\u003e\n        \u003ca href=\"/about\"\u003eAbout\u003c/a\u003e\n        \u003ca href=\"/blog\"\u003eBlog\u003c/a\u003e\n      \u003c/nav\u003e\n      \u003crouter /\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n### Nav Links\n\nLinks under the `\u003cnav\u003e` element will be treated as navigation links by the router. When the `href` of a nav link matches a route, an active class will be added to the link element. By default, the active class is `active`, but you can customize it by setting the `data-active-class` attribute on the `\u003cnav\u003e` element. You can add styles for the active link using nested CSS selectors in the `style` attribute of the `\u003cnav\u003e` element.\n\n```tsx\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml request={req} routes={routes}\u003e\n      \u003cnav style={{ \"\u0026 a.active\": { fontWeight: \"bold\" } }} data-active-class=\"active\"\u003e\n        \u003ca href=\"/\"\u003eHome\u003c/a\u003e\n        \u003ca href=\"/about\"\u003eAbout\u003c/a\u003e\n        \u003ca href=\"/blog\"\u003eBlog\u003c/a\u003e\n      \u003c/nav\u003e\n      \u003crouter /\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n### Fallback (404)\n\nYou can add fallback(404) content to the `\u003crouter\u003e` element as children, which will be displayed when no route matches the current URL.\n\n```tsx\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml request={req} routes={routes}\u003e\n      \u003crouter\u003e\n        \u003cp\u003ePage Not Found\u003c/p\u003e\n        \u003cp\u003eBack to \u003ca href=\"/\"\u003eHome\u003c/a\u003e\u003c/p\u003e\n      \u003c/router\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n## Using Session\n\nmono-jsx provides a built-in session storage that allows you to manage user sessions. To use the session storage, you need to set the `session` prop on the root `\u003chtml\u003e` element with the `cookie.secret` option.\n\n```tsx\nfunction Index(this: FC) {\n  const user = this.session.get\u003c{ name: string }\u003e(\"user\")\n  if (!user) {\n    return \u003cp\u003ePlease \u003ca href=\"/login\"\u003eLogin\u003c/a\u003e\u003c/p\u003e\n  }\n  return \u003cp\u003eWelcome, {user.name}!\u003c/p\u003e\n}\n\nasync function Login(this: FC) {\n  return (\n    \u003cform route\u003e\n      \u003cinput type=\"text\" name=\"username\" placeholder=\"Username\" /\u003e\n      \u003cinput type=\"password\" name=\"password\" placeholder=\"Password\" /\u003e\n      \u003cbutton type=\"submit\"\u003eLogin\u003c/button\u003e\n    \u003c/form\u003e\n  )\n}\n\nLogin.FormHandler = async function(this: FC, data: FormData) {\n  const user = await auth(data)\n  if (!user) {\n    return \u003cinvalid for=\"username,password\"\u003eInvalid Username/Password\u003c/invalid\u003e\n  }\n  this.session.set(\"user\", user)\n  return \u003credirect to=\"/\" /\u003e\n}\n\nconst routes = {\n  \"/\": Index,\n  \"/login\": Login,\n}\n\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml request={req} routes={routes} session={{ cookie: { secret: \"...\" } }}\u003e\n      \u003crouter /\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n### Session Storage API\n\n```ts\nfunction Component(this: FC) {\n  // set a value in the session\n  this.session.set(\"user\", { name: \"John\" })\n  // get a value from the session\n  this.session.get\u003c{ name: string }\u003e(\"user\") // { name: \"John\" }\n  // get all entries from the session\n  this.session.entries() // [[\"user\", { name: \"John\" }]]\n  // delete a value from the session\n  this.session.delete(\"user\")\n  // destroy the session\n  this.session.destroy()\n}\n```\n\n## Caching\n\nmono-jsx renders HTML dynamically per request; large apps may tax your CPU resources. To improve rendering performance, mono-jsx introduces two built-in elements that can cache the rendered HTML of the children:\n\n- `\u003ccache\u003e` with specified `key` and `maxAge`\n- `\u003cstatic\u003e` for elements that rarely change, such as `\u003csvg\u003e`\n\n```tsx\nfunction BlogPage() {\n  return (\n    \u003ccache key=\"blog\" maxAge={86400}\u003e\n      \u003cBlog /\u003e\n    \u003c/cache\u003e\n  )\n}\n\nfunction Icon() {\n  return (\n    \u003cstatic\u003e\n      \u003csvg\u003e...\u003c/svg\u003e\n    \u003c/static\u003e\n  )\n}\n```\n\n## Customizing HTML Response\n\nYou can add `status` or `headers` attributes to the root `\u003chtml\u003e` element to customize the HTTP response:\n\n```tsx\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml\n      status={404}\n      headers={{\n        cacheControl: \"public, max-age=0, must-revalidate\",\n        setCookie: \"name=value\",\n        \"x-foo\": \"bar\",\n      }}\n    \u003e\n      \u003ch1\u003ePage Not Found\u003c/h1\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n### Using htmx\n\nmono-jsx integrates with [htmx](https://htmx.org/) and [typed-htmx](https://github.com/Desdaemon/typed-htmx). To use htmx, add the `htmx` attribute to the root `\u003chtml\u003e` element:\n\n```tsx\nexport default {\n  fetch: (req) =\u003e {\n    const url = new URL(req.url);\n\n    if (url.pathname === \"/clicked\") {\n      return (\n        \u003chtml\u003e\n          \u003cspan\u003eClicked!\u003c/span\u003e\n        \u003c/html\u003e\n      );\n    }\n\n    return (\n      \u003chtml htmx\u003e\n        \u003cbutton hx-get=\"/clicked\" hx-swap=\"outerHTML\"\u003e\n          Click Me\n        \u003c/button\u003e\n      \u003c/html\u003e\n    )\n  }\n}\n```\n\n#### Adding htmx Extensions\n\nYou can add htmx [extensions](https://htmx.org/docs/#extensions) by adding the `htmx-ext-*` attribute to the root `\u003chtml\u003e` element:\n\n```tsx\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml htmx htmx-ext-response-targets htmx-ext-ws\u003e\n      \u003cbutton hx-get=\"/clicked\" hx-swap=\"outerHTML\"\u003e\n        Click Me\n      \u003c/button\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n#### Specifying htmx Version\n\nYou can specify the htmx version by setting the `htmx` attribute to a specific version:\n\n```tsx\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml htmx=\"2.0.4\" htmx-ext-response-targets=\"2.0.2\" htmx-ext-ws=\"2.0.2\"\u003e\n      \u003cbutton hx-get=\"/clicked\" hx-swap=\"outerHTML\"\u003e\n        Click Me\n      \u003c/button\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n#### Setting Up htmx Manually\n\nBy default, mono-jsx imports htmx from the [esm.sh](https://esm.sh/) CDN when you set the `htmx` attribute. You can also set up htmx manually with your own CDN or local copy:\n\n```tsx\nexport default {\n  fetch: (req) =\u003e (\n    \u003chtml\u003e\n      \u003chead\u003e\n        \u003cscript src=\"https://unpkg.com/htmx.org@2.0.4\" integrity=\"sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+\" crossorigin=\"anonymous\"\u003e\u003c/script\u003e\n        \u003cscript src=\"https://unpkg.com/htmx-ext-ws@2.0.2\" integrity=\"sha384-vuKxTKv5TX/b3lLzDKP2U363sOAoRo5wSvzzc3LJsbaQRSBSS+3rKKHcOx5J8doU\" crossorigin=\"anonymous\"\u003e\u003c/script\u003e\n      \u003c/head\u003e\n      \u003cbody\u003e\n        \u003cbutton hx-get=\"/clicked\" hx-swap=\"outerHTML\"\u003e\n          Click Me\n        \u003c/button\u003e\n      \u003c/body\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fije%2Fmono-jsx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fije%2Fmono-jsx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fije%2Fmono-jsx/lists"}