{"id":25972824,"url":"https://github.com/radishland/radish","last_synced_at":"2025-07-13T13:37:40.257Z","repository":{"id":279235306,"uuid":"937053617","full_name":"radishland/radish","owner":"radishland","description":"A full-stack framework built around Web Components and Web Standards","archived":false,"fork":false,"pushed_at":"2025-03-04T14:22:02.000Z","size":284,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-03-04T15:28:09.903Z","etag":null,"topics":["webcomponents","webcomponents-framework"],"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/radishland.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-02-22T08:10:00.000Z","updated_at":"2025-03-04T14:22:03.000Z","dependencies_parsed_at":null,"dependency_job_id":"ec89caaa-7b48-458a-833a-bc0d0b58f599","html_url":"https://github.com/radishland/radish","commit_stats":null,"previous_names":["radishland/radish"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radishland%2Fradish","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radishland%2Fradish/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radishland%2Fradish/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radishland%2Fradish/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/radishland","download_url":"https://codeload.github.com/radishland/radish/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241945530,"owners_count":20046870,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["webcomponents","webcomponents-framework"],"created_at":"2025-03-05T01:17:33.153Z","updated_at":"2025-07-13T13:37:40.238Z","avatar_url":"https://github.com/radishland.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Radish!\n\nRadish is a standards-first framework with a unified approach to building\nfullstack web apps.\n\n- **[Unified Approach](#mental-model)**: A cohesive and simple mental model\n- **Standards-first**: Embraces Web Components by focusing on the good parts\n- **Server-Side rendering**: Supports declarative shadow root templates with SSR\n- **Declarative API**: [Declarative directives](#directives) and\n  [signals](#reactivity) for reactivity\n- **Readable code**: Near-zero [build](#build) step and\n  [no bundling](#no-bundle), making code readable and debuggable\n- **[Type Safety](#type-safety)**: Type-safe authoring\n- **Powerful [Effect System](#effect-system)**\n- **Extensible [Plugin API](#plugin-api)**\n- **Secure by Default**: Powered by Deno\n\n## Introduction\n\nThe web platform is rapidly maturing, with features arriving at an unprecedented\npace: HTML declarative shadow root, CSS functions, JS Signals, Navigation API,\nand more. Relying on the platform means less churn: web APIs evolve slowly,\nreducing migration overhead.\n\nToday we can manage frontend dependencies with importmaps and create modular\ncode with native ES modules. The future is moving beyond traditional bundlers,\nfreeing us from JavaScript toolchain sprawl.\n\nRadish is designed to offer top-tier features, DX, maintainability and\nfuture-proofing, while minimizing abstraction, bundling, and deviation from web\nstandards.\n\nTry it out, and you'll discover how refreshing it is to have readable and\ndebuggable code in the browser at every stage (we just strip types). Radish\ndeepens your understanding of platform technologies and helps you build more\nrobust, future-proof applications. Its clear and coherent\n[mental model](#overview) helps everything click into place.\n\n## Try-out the alpha\n\n**Create a new project:**\n\n```sh\ndeno run -A jsr:@radish/init@1.0.0-alpha.33 my-rad-project\n```\n\n**Build your project:**\n\n```sh\ndeno task build\n```\n\n**Start your project:**\n\n```sh\ndeno task start --dev\n```\n\n### Examples\n\nHave a look at the [`/app`](https://github.com/radishland/radish/tree/main/app)\nfolder of the repo for some syntax examples\n\nHere's how simple it is to declaratively bind a checkbox to an element property:\n\n```ts\nimport { HandlerRegistry, signal } from \"radish\";\n\n// demo-bind.ts\nclass DemoBind extends HandlerRegistry {\n  isChecked = signal(true);\n}\n\ncustomElements.define(\"handle-input-demo\", HandleInputDemo);\n```\n\n```html\n\u003cdemo-bind\u003e\n  \u003cinput type=\"checkbox\" bind:checked=\"isChecked\" /\u003e\n\u003c/demo-bind\u003e\n```\n\n### Project structure\n\nA Radish project looks like this:\n\n```\nmy-rad-project/\n├ elements/ \u003c-- your custom elements, web components and unknown elements\n├ lib/      \u003c-- reusable ts modules\n├ routes/   \u003c-- routes and one-off colocated custom elements\n├ static/   \u003c-- static assets that should be served as-is\n├ start.ts  \u003c-- start script and project config\n└ deno.json\n```\n\n- [Radish!](#radish)\n  - [Introduction](#introduction)\n  - [Try-out the alpha](#try-out-the-alpha)\n    - [Examples](#examples)\n    - [Project structure](#project-structure)\n  - [Mental model](#mental-model)\n  - [Effect system](#effect-system)\n  - [Plugin API](#plugin-api)\n  - [Routing](#routing)\n    - [Dynamic routes](#dynamic-routes)\n    - [Non-capturing groups](#non-capturing-groups)\n    - [Regex matchers](#regex-matchers)\n  - [Navigation](#navigation)\n    - [Speculation Rules](#speculation-rules)\n  - [Elements](#elements)\n  - [Authoring](#authoring)\n    - [Type-safety](#type-safety)\n    - [Auto-imports](#auto-imports)\n    - [Debugging](#debugging)\n  - [Scoped Handler Registry](#scoped-handler-registry)\n  - [Reactivity](#reactivity)\n  - [Directives](#directives)\n    - [attr directive](#attr-directive)\n    - [bind directive: declarative two-way bindings](#bind-directive-declarative-two-way-bindings)\n    - [bool directive](#bool-directive)\n    - [classList directive](#classlist-directive)\n    - [html directive](#html-directive)\n    - [text directive](#text-directive)\n    - [on directive: declarative event handlers](#on-directive-declarative-event-handlers)\n    - [prop directive](#prop-directive)\n    - [use directive: declarative hooks](#use-directive-declarative-hooks)\n  - [Special elements](#special-elements)\n    - [head](#head)\n  - [Build](#build)\n    - [Importmap](#importmap)\n    - [No bundle](#no-bundle)\n  - [Resources](#resources)\n\n## [Mental model](/guides/MENTAL_MODEL.md)\n\nRadish simple mental model helps you make sense of all the moving parts in a\nfullstack app, binging them into a cohesive picture, and giving you a glimpse of\nthe framework's modularity.\n\n## [Effect system](https://jsr.io/@radish/effect-system)\n\nThe effect-system is built around effects you perform, and handlers to interpret\nthem, usually via [plugins](#plugin-api).\n\n\u003cdetails\u003e\n  \u003csummary\u003eNote: awaiting effects\u003c/summary\u003e\n  \u003chr\u003e\n  \u003cp\u003e\n    Effects are often sequenced in pipelines like read -\u003e transform -\u003e write, hinting at their \u003ca href=\"https://www.sciencedirect.com/science/article/pii/0890540191900524\"\u003emonadic\u003c/a\u003e nature.\n  \u003c/p\u003e\n  \u003cp\u003e\n   In Radish, handlers interpret the `Effect\u0026ltT\u0026gt` monad into the `Promise\u0026ltT\u0026gt` monad letting us \u003ccode\u003eawait\u003c/code\u003e them for clean, direct sequencing.\n  \u003c/p\u003e\n  \u003cp\u003e\n    \u003ccode\u003eawait\u003c/code\u003e is just syntax sugar offered by the `PromiseLike` interface. It's the JS equivalent of Haskell's \u003ca href=\"https://en.wikibooks.org/wiki/Haskell/do_notation\"\u003edo-notation\u003c/a\u003e\n  \u003c/p\u003e\n  \u003chr\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eNote: JS async marker and handler types\u003c/summary\u003e\n  \u003chr\u003e\n  \u003cp\u003e\n    In JavaScript/TypeScript, asynchrony is the only effect we have markers for, with the `async` keyword and the `Promise` return type. Other effects (throwing, logging) have no markers.\n  \u003c/p\u003e\n  \u003cp\u003e\n    One approach would be to encode all effects in types. This is the approach taken by the \u003ca href=\"https://effect.website/\"\u003eEffect framework\u003c/a\u003e.\n  \u003c/p\u003e\n  \u003cp\u003e\n    Instead, Radish is a lightweight approach that embraces the JavaScript/TypeScript languages, with no need to wrap all your libraries and with no interop concerns: it's all standard JavaScript.\n  \u003c/p\u003e\n  \u003cp\u003e\n    In operations signatures (see \u003ccode\u003e\u003ca href=\"https://jsr.io/@radish/effect-system/doc/~/createEffect\"\u003ecreateEffect\u003c/a\u003e\u003c/code\u003e), asynchrony is treated like any other JS effect: it's swallowed and we don't mark it in the operation signature. This provides a uniform treatment of effects in operation signatures as well as flexibility in how handlers are implemented: an operation signature corresponds to an effect-free signature, and being async becomes an implementation detail. This also lets handlers perform other effects (by awaiting them) and, by the current note, this is an implementation detail too.\n  \u003c/p\u003e\n  \u003chr\u003e\n\u003c/details\u003e\n\nThe full documentation of the effect-system is available\n[here](https://jsr.io/@radish/effect-system)\n\n## Plugin API\n\nA plugin is just an object with a name and an array of handlers\n\n```ts\nimport type { Plugin } from \"radish/types\";\n\nexport const myIOPlugin: Plugin = {\n  name: \"my-io-plugin\",\n  handlers: [\n    IOCountTXTReads,\n    IODecorateTXT,\n    IOHandleTXTOnly,\n    IOReadHandler,\n    IOWriteHandler,\n  ],\n};\n```\n\nWhen handlers rely on delegation (`Handler.continue(...)`), the **order\nmatters**. Handlers are evaluated in sequence with the first handler of the list\nbeing executed first.\n\nAll built-in plugin handlers in Radish are total, so you can safely build\nspecialized handlers that delegate or decorate them.\n\nOnce your plugin is ready, extend Radish's behavior by prepending it to the\n`plugins` array of your config file.\n\nAll core framework features, like declarative shadow root inlining, server\ndirectives, type stripping etc., are implemented as built-in plugins. You can\nextend, override, or layer on top of them with the plugin API.\n\nThe provided plugins can be imported from `radish/plugins`, see the\n[core/src/plugins](https://github.com/radishland/radish/tree/main/core/src/plugins)\nfolder. Here's an overview\n\n## Routing\n\nRadish uses a file-based router based on the\n[`URLPattern`](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API)\nWeb Standard. Routes correspond to subfolders of the `routes` folder with an\n`index.html` file inside\n\nExample: The folder structure\n\n```\nroutes/\n└ user/\n  └ profile/\n    └ index.html\n```\n\ncorresponds to the route `/user/profile`.\n\n### Dynamic routes\n\nA dynamic segment can be provided with square brackets:\n\nExample:\n\n```\nroutes/\n└ user/\n  └ [id]/\n    └ index.html\n```\n\nThis folder structure corresponds to the named group `/user/:id` and will match\nagainst `/user/123` for example\n\n### Non-capturing groups\n\nA non-captured group is delimited by curly braces `{}`, and can be made optional\nwith the `?` group modifier.\n\nExample: The pattern `book{s}?` matches both `/book` and `/books`\n\n```\nroutes/\n└ books{s}?/\n  └ index.html\n```\n\n### Regex matchers\n\nTo ensure a parameter is valid you can provide named Regex matchers to the\nrouter.\n\nExample. To make sure a user id is a number, add the\n`router: { matchers: { number: /\\d+/ } }` option to the config and update the\nroute:\n\n```\nroutes/\n└ user/\n  └ [id=number]/\n    └ index.html\n```\n\nOnly non-empty numeric ids will match against this route, like `/user/123` but\nnot `/user/abc`.\n\n## Navigation\n\n### Speculation Rules\n\nThe\n[Speculation Rules API](https://developer.chrome.com/docs/web-platform/prerender-pages)\nis supported with the generation of a `speculationrules` script at build time\nfor instant page navigation. You can configure the ruleset in the `generate.ts`\nscript build options.\n\n## Elements\n\nThe `elements` folder contains all three sorts of elements:\n\n- custom elements, with no template and a only a class export\n- unknown elements, with only an html template and no associated custom element\n- web components, with both an html template and a custom element\n\nThe convention is that an element's folder and files are named after the\nelement's tag name:\n\n- `app/elements/my-element/my-element.html` contains the declarative shadow root\n  template for `my-element`.\n- `app/elements/my-element/my-element.ts` contains the `MyElement` class\n  defining the custom element `my-element`.\n\nDeclarative shadowroot templates are inlined at build time\n\n1. Custom element templates inside `app/elements/` must have the\n   `shadowrootmode=\"open\"` attribute to allow SSR.\n\n\u003c!-- ## Styles\n\n- the provided `reset.css`\n- the `generated_variables.css` for just-in-time CSS variable definitions, fluid sizes and typography, and a flexible, customizable and intuitive scale --\u003e\n\n## Authoring\n\n### Type-safety\n\nYou can write your modules in Typescript and type annotations will be removed\nwith [type-strip](https://github.com/fcrozatier/type-strip).\n\nOnly _modern TypeScript_ is supported, equivalent to setting\n[`--erasableSyntaxOnly`](https://devblogs.microsoft.com/typescript/announcing-typescript-5-8-beta/#the---erasablesyntaxonly-option).\nSee the `type-strip`\n[unsupported features](https://github.com/fcrozatier/type-strip?tab=readme-ov-file#unsupported-features)\nfor more.\n\nThis limitation is on purpose so that your code is not\n[incompatible](https://github.com/tc39/proposal-type-annotations) with the TC39\ntype annotations proposal.\n\n### Auto-imports\n\nYour custom elements are automatically imported by Radish where you use them.\n\nFor example if you have defined a `my-button` web-component, then you can use it\ndirectly in any page, and Radish will add the required import in the head of the\npage:\n\n```html\n\u003c!-- This is automatically inserted in the head --\u003e\n\u003cscript type=\"module\"\u003e\n  import \"/elements/my-button/my-button.js\";\n\u003c/script\u003e\n```\n\n### Debugging\n\nDebugging your app is quite simple - and it's rather fun! - as Deno runs\nTypeScript source directly, so you can easily step through Radish very readable\nts source code and not be confused by compiled/minified js.\n\nA VS-Code `launch.json` file is provided in the `.vscode` folder of your app to\nhelp in the process. Just pass it the args array of the script you want to debug\n(`\"--importmap\"`, `\"--build\"` etc) and launch the debug session!\n\nIn the browser debugging also works out of the box, and you can easily step\nthrough your code to understand what's going on, since the code running in the\nbrowser is just your TypeScript code with the types stripped out, which should\nbe easy to read and a seamless experience.\n\n## Scoped Handler Registry\n\nA scoped handler registry is a custom element extending the `HandlerRegistry`\nclass. This is where you can define handlers for various directives listed\nbelow.\n\nOnce in your markup, a handler registry handles all the interaction requests\nfrom its subtree of elements if it implements the requested handler. Handler\nregistries are scoped: only the closest parent of a given element will handle\nits interactions if it can.\n\nIn this example, the `handle-hover` custom element implements the `showTooltip`\nevent handler and the `handle-click` implements `handleClick`.\n\n```html\n\u003chandle-hover\u003e\n  ...\n  \u003chandle-click\u003e\n    ...\n    \u003cbutton on:click=\"handleClick\" on:mouseover=\"showTooltip\"\u003eclick or hover me\u003c/button\u003e\n  \u003chandle-click\u003e\n\u003chandle-hover\u003e\n```\n\nThis allows you to have a top-level registry implementing common handlers or\nhooks and removes the need for props drilling\n\n## Reactivity\n\nThe reactivity module is built around `@preact/signals-core` and provides the\nfollowing helpers:\n\n- the `signal\u003cT\u003e(value: T)` helper function creates a signal whose value can be\n  accessed and modified in the code with the `.value` property. Inside templates\n  signals are coerced to their value and can be referenced directly without\n  `.value`\n\nExample: given the field `name = signal(\"Radish\")` in a parent handler, we can\nreference it directly:\n\n```html\n\u003cparent-handler\u003e{name}\u003c/parent-handler\u003e\n```\n\n- the `computed(computation: () =\u003e void)` helper creates a read-only computed\n  signal based on the values of other signals and is used similarly to a\n  `signal`\n\n- the `reactive\u003cT\u003e(value: T)` helper creates a deeply reactive object or array.\n  A reactive object or array is proxied and its properties can be accessed\n  directly without `.value`\n\n```ts\nconst obj = reactive({ a: { b: 1 } }); // A reactive object\nconst a = computed(() =\u003e obj.a.b); // A reactive object is proxied and its properties can be accessed directly without `.value`\n\nobj.a.b = 2; // Deep reactivity\nconsole.log(a); // 2\n```\n\n- Handler Registries have a `this.effect(() =\u003e void)` method to create an effect\n  which is automatically cleaned up when the element is disconnected. For\n  advanced use cases an unowned effect can be created directly with the `effect`\n  helper and accepts an `AbortSignal`\n\n## Directives\n\n- [`attr`](#attr-directive)\n- [`bind`](#bind-directive-declarative-two-way-bindings)\n- [`bool`](#bool-directive)\n- [`classList`](#classList-directive)\n- [`html`](#html-directive)\n- [`on`](#on-directive-declarative-event-handlers)\n- [`prop`](#prop-directive)\n- [`text`](#text-directive)\n- [`use`](#use-directive-declarative-hooks)\n\n`on`, `prop` and `use` only have client semantics while the other directives are\nuniversal: they have both client and server semantics\n\n### attr directive\n\nThe `attr` directive sets an attribute on an element to the value referenced by\na given identifier. If the identifier is a signal, then the assignment is\nreactive\n\n```html\n\u003cinput type=\"checkbox\" attr:disabled=\"isDisabled\" /\u003e\n```\n\nIf the attribute and the identifier have the same name we can use a shorthand\nnotation:\n\n```html\n\u003c!-- these are equivalent --\u003e\n\u003cinput type=\"checkbox\" attr:id /\u003e\n\u003cinput type=\"checkbox\" attr:id=\"id\" /\u003e\n```\n\nIn the previous example, the `id` attribute of the input is bound to the `id`\nproperty of its surrounding handler.\n\n### bind directive: declarative two-way bindings\n\nThe `bind` directive declares a two-way binding between an element stateful\nproperty and a reactive signal.\n\nFor example to bind the `checked` property of an `input` to the `isChecked`\nsignal of a surrounding handler:\n\n```html\n\u003cdemo-bind\u003e\n  \u003cinput type=\"checkbox\" bind:checked=\"isChecked\" /\u003e\n\u003c/demo-bind\u003e\n```\n\n```ts\n// demo-bind.ts\nclass DemoBind extends HandlerRegistry {\n  isChecked = signal(true);\n}\n```\n\nIf the property and the value have the same name you can use the following\nshorthand syntax:\n\n```html\n\u003c!-- these are equivalent --\u003e\n\u003cinput type=\"checkbox\" bind:checked=\"checked\" /\u003e\n\u003cinput type=\"checkbox\" bind:checked /\u003e\n```\n\nThe `bind` directive is a universal directive, with both client and server\nsemantics:\n\n- On the server, it is equivalent to `attr` and sets the attribute to the given\n  value.\n- On the client, `bind` is similar to `prop`, with the signal value first\n  resumed to the value of the HTML state, in case the user interacted before js\n  was ready. Then the prop and state are kept in sync.\n\nThe resumability of the state on the client prevents janky hydration with slow\nnetworks. And focus is not lost in the process.\n\nAlso, the `bind` directive allows cross-component bindings at any filiation\nlevel: parents, grand-parents, grand-grand-parents etc.\n\nYou can use this directive on web components too. For example the following\n`my-rating` element and the `input` are correlated via the `value` signal of\ntheir common handler:\n\n```html\n\u003cbind-custom-element\u003e\n  \u003cinput type=\"number\" bind:value\u003e\n  \u003cmy-rating label=\"Rating\" bind:value\u003e\u003c/my-rating\u003e\n\u003cbind-custom-element\u003e\n```\n\n```ts\nclass BindCustomElement extends HandlerRegistry {\n  value = signal(3);\n}\n```\n\n### bool directive\n\nThe `bool` directive handles custom boolean attribute bindings.\n\n```html\n\u003cdemo-bool\u003e\n  \u003clabel\u003e\n    loading \u003cinput type=\"checkbox\" name=\"circle\" bind:checked=\"loading\"\u003e\n  \u003c/label\u003e\n\n  \u003csl-button size=\"medium\" bool:loading\u003e\n    \u003csl-icon name=\"gear\" label=\"Settings\"\u003e\u003c/sl-icon\u003e\n  \u003c/sl-button\u003e\n\u003c/demo-bool\u003e\n```\n\n```ts\nclass DemoBool extends HandlerRegistry {\n  loading = signal(true);\n}\n```\n\nToggling the checkbox will add or remove the \u003ccode\u003eloading\u003c/code\u003e boolean\nattribute on the \u003ccode\u003esl-button\u003c/code\u003e web component.\n\nGlobal boolean attributes like \u003ccode\u003edisabled\u003c/code\u003e, \u003ccode\u003echecked\u003c/code\u003e etc.\ncan also be handled by the `attr` and `prop` directives.\n\n### classList directive\n\nThe `classList` directive accepts a reactive object where keys are strings of\nspace separated class names and values are boolean values or signals.\n\nExample:\n\n```ts\nexport class HandleClass extends HandlerRegistry {\n  outline = signal(false);\n  classes = reactive({\n    \"red\": false,\n    \"outline\": this.outline,\n  });\n\n  toggleColor() {\n    this.object.red = !this.object.red;\n  }\n\n  toggleOutline() {\n    this.outline.value = !this.outline.value;\n  }\n}\n```\n\n```html\n\u003chandle-class\u003e\n  \u003cp classList=\"classes\"\u003eI have reactive classes\u003c/p\u003e\n\n  \u003cbutton on:click=\"toggleColor\"\u003etoggle color\u003c/button\u003e\n  \u003cbutton on:click=\"toggleOutline\"\u003etoggle outline\u003c/button\u003e\n\u003c/handle-class\u003e\n```\n\nIn this example clicking the buttons toggles the `.red` and `.outline` classes\non the paragraph element\n\n### html directive\n\nThe `html` directive sets the `innerHTML` property of an element. On the server\nit parses the provided html string and inserts the resulting nodes as children\nof the element.\n\n### text directive\n\nThe `text` directive sets the `textContent` property of an element. On the\nserver it creates a child text node inside on the element.\n\n### on directive: declarative event handlers\n\nThe `on` directive allows to declaratively add event-handlers to any element:\n\n```html\n\u003cbutton on:click=\"handleClick\" on:mouseover=\"handleMouseOver\"\u003e\n  click or hover me\n\u003c/button\u003e\n```\n\nYou can add multiple event handlers, even with the same event type, as `on` is a\ndeclarative way to `addEventListener`. For example, this button has two click\nevent handlers:\n\n```html\n\u003cbutton on:click=\"handleClick\" on:click=\"log\"\u003eclick me\u003c/button\u003e\n```\n\n### prop directive\n\nThe `prop` directive sets an element properties on the client.\n\nIt also gives fine grained control when you want to make sure js is available\nlike when toggling an aria property. In case js is not available the `prop`\neffect doesn't run, so the property is not set and the element doesn't end-up\nstuck in the wrong accessibility state.\n\n### use directive: declarative hooks\n\nThe `use` directive runs a lifecycle hook on an element.\n\n```html\n\u003chandle-hook\u003e\n  \u003cspan use:hook\u003eI'm hooked\u003c/span\u003e\n\u003c/handle-hook\u003e\n```\n\nThe closest handlers registry implementing the `hook` method will handle it\n\n```ts\nexport class HandleHook extends HandlerRegistry {\n  hook(element: Element) {\n    element.style.color = \"red\";\n\n    element.addEventListener(\"pointerover\", () =\u003e {\n      element.style.color = \"green\";\n    });\n  }\n}\n```\n\nYou can use a hook defined in a parent handler registry, similar to if it were\nautomatically passed via a context API\n\n## Special elements\n\n### head\n\nUse the `head` element at the top level of pages to declaratively add content to\nthe document's head, like providing a title, description etc.\n\n```html\n\u003chead\u003e\n  \u003ctitle\u003eThe page title\u003c/title\u003e\n\u003c/head\u003e\n```\n\n## Build\n\nBuilding your projects mainly consists of stripping types, generating an\nimportmap and applying server effects like declarative shadow root inlining.\n\n### Importmap\n\nWhen building your project, an\n[importmap](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap)\nof your runtime dependencies is automatically generated and inserted in the\n`\u003chead\u003e`.\n\n\u003c!-- In dev mode the importmap resolves modules from the node_modules folder by default and allows off-line development, and in production it resolves modules from the [esm.sh](https://esm.sh/) CDN. --\u003e\n\nThe importmap resolves modules from the [esm.sh](https://esm.sh/) CDN:\n\n- both [npm](https://www.npmjs.com/) and [jsr](https://jsr.io/) modules are\n  handled,\n- the build target is automatically determined by checking the `User-Agent`\n  header. So users of your site get precisely what they need\n\nThe importmap can be generated with the following command:\n\n```sh\ndeno task generate --importmap\n```\n\nYou have full control over the importmap in your config file, with options for\nmanually including packages.\n\n### No bundle\n\nThe [importmap](#importmap) lets the browser resolve dependencies (and\nhigher-order dependencies) from the [esm.sh](https://esm.sh/) CDN. This means\nyour code and its dependencies are not bundled together, and instead there is a\nclean separation between the code that you author and everything else. This\nallows them to move on asynchronously at their own pace and has several\nbenefits:\n\n- Better caching. Dependencies can be cached by the browser separately from your\n  modules, _e.g._ updating a typo in your code only invalidates that file.\n- Smaller downloads. Since dependencies are not inlined with your code, they're\n  only downloaded on first load or whenever you update their version — not with\n  every bundle.\n- Less bandwidth usage. Resolving dependencies client-side and downloading them\n  from CDNs means that much less traffic on your infrastructure. This can make a\n  difference in terms of cost and usage.\n\n## Resources\n\n- The\n  [importmap spec](https://html.spec.whatwg.org/multipage/webappapis.html#import-maps)\n- [Declarative Shadow DOM](https://web.dev/articles/declarative-shadow-dom)\n- [Custom Element Best Practices](https://web.dev/articles/custom-elements-best-practices)\n- MDN 3 parts guide:\n  - [Using custom elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements)\n  - [Using Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM)\n  - [Using templates and slots](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots)\n\n[^alg-effects]: What is algebraic about algebraic effects and handlers? — ANDREJ\n    BAUER. https://arxiv.org/pdf/1807.05923\n\n[^ui-react]: 2015 blog post describing\n    [UI as a function of state](https://www.kn8.lt/blog/ui-is-a-function-of-data/)\n\n[^ui-overreacted]: Overreacted blog post describing the UI=f(state, data)\n    formula. https://overreacted.io/the-two-reacts/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fradishland%2Fradish","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fradishland%2Fradish","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fradishland%2Fradish/lists"}