{"id":20129361,"url":"https://github.com/ryanmorr/reflex","last_synced_at":"2026-05-13T03:03:41.808Z","repository":{"id":45466139,"uuid":"291771498","full_name":"ryanmorr/reflex","owner":"ryanmorr","description":"Reactive DOM","archived":false,"fork":false,"pushed_at":"2023-05-15T18:27:30.000Z","size":378,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-13T14:40:02.201Z","etag":null,"topics":["dom","javascript","reactive","signal","ui"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ryanmorr.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":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-08-31T16:46:21.000Z","updated_at":"2024-11-26T06:54:49.000Z","dependencies_parsed_at":"2024-11-13T20:35:31.710Z","dependency_job_id":"bffa3df5-c63c-44ec-93fa-d5f0b004af42","html_url":"https://github.com/ryanmorr/reflex","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanmorr%2Freflex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanmorr%2Freflex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanmorr%2Freflex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanmorr%2Freflex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ryanmorr","download_url":"https://codeload.github.com/ryanmorr/reflex/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241573111,"owners_count":19984422,"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":["dom","javascript","reactive","signal","ui"],"created_at":"2024-11-13T20:33:52.951Z","updated_at":"2026-05-13T03:03:36.764Z","avatar_url":"https://github.com/ryanmorr.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# reflex\n\n[![Version Badge][version-image]][project-url]\n[![License][license-image]][license-url]\n[![Build Status][build-image]][build-url]\n\n\u003e Reactive DOM\n\n## Install\n\nDownload the [CJS](https://github.com/ryanmorr/reflex/raw/master/dist/cjs/reflex.js), [ESM](https://github.com/ryanmorr/reflex/raw/master/dist/esm/reflex.js), [UMD](https://github.com/ryanmorr/reflex/raw/master/dist/umd/reflex.js) versions or install via NPM:\n\n```sh\nnpm install @ryanmorr/reflex\n```\n\n## Usage\n\nReflex is a small, but versatile UI library that combines declarative DOM building with reactive stores that bind data to DOM nodes, automatically keeping the DOM in sync when the data is changed:\n\n```javascript\nimport { html, store } from '@ryanmorr/reflex';\n\nconst count = store(0);\n\nconst element = html`\n    \u003cdiv\u003e\n        \u003cp\u003eCount: ${count}\u003c/p\u003e\n        \u003cbutton onclick=${() =\u003e count.update((val) =\u003e val + 1)}\u003eIncrement\u003c/button\u003e\n    \u003c/div\u003e\n`;\n\ndocument.body.appendChild(element);\n```\n\n## API\n\n### `store(value?)`\n\nCreate a reactive store that encapsulates a value and can notify subscribers when the value changes:\n\n```javascript\nimport { store } from '@ryanmorr/reflex';\n\n// Create a store with an initial value\nconst count = store(0);\n\n// Get the store value\ncount.value(); //=\u003e 0\n\n// Set the store value\ncount.set(1);\n\n// Set the store value with a callback function\ncount.update((val) =\u003e val + 1);\n\n// Subscribe a callback function to be invoked when the value changes,\n// it returns a function to unsubscribe from future updates\nconst unsubscribe = count.subscribe((nextVal, prevVal) =\u003e {\n    // Do something\n});\n```\n\n------\n\n### `derived(...stores, callback)`\n\nCreate a reactive store that is based on the value of one or more other stores:\n\n```javascript\nimport { derived, store } from '@ryanmorr/reflex';\n\nconst firstName = store('John');\nconst lastName = store('Doe');\nconst fullName = derived(firstName, lastName, (first, last) =\u003e `${first} ${last}`);\n\nfullName.value(); //=\u003e \"John Doe\"\n\nfirstName.set('Jane');\n\nfullName.value(); //=\u003e \"Jane Doe\"\n\n// Subscribe to be notified of changes\nconst unsubscribe = fullName.subscribe((nextVal, prevVal) =\u003e {\n    // Do something\n});\n```\n\nIf the callback function defines an extra parameter in its signature, the derived store is treated as asynchronous. The callback function is provided a setter for the store's value and no longer relies on the return value:\n\n```javascript\nimport { derived, store } from '@ryanmorr/reflex';\n\nconst query = store();\n\n// Perform an ajax request when the query changes\n// and notify subscribers with the results\nconst results = derived(query, (string, set) =\u003e {\n    fetch(`path/to/server/${encodeURIComponent(string)}`).then(set);\n});\n```\n\n------\n\n### `html(strings, ...values?)`\n\nCreate DOM nodes declaratively via tagged template literals:\n\n```javascript\nimport { html } from '@ryanmorr/reflex';\n\n// Create an element\nconst el = html`\u003cdiv\u003e\u003c/div\u003e`;\n\n// Create a text node\nconst text = html`Hello World`;\n\n// Create an SVG element\nconst rect = html`\u003crect x=\"10\" y=\"10\" width=\"100\" height=\"100\" /\u003e`;\n\n// Create a document fragment for multiple root nodes\nconst frag = html`\u003cdiv\u003e\u003c/div\u003e\u003cspan\u003e\u003c/span\u003e`;\n\n// Supports attributes\nconst div = html`\u003cdiv id=\"foo\" class=${'bar'} /\u003e`;\n\n// Supports spread attributes\nconst section = html`\u003csection ...${{id: 'foo', class: 'bar'}} /\u003e`;\n\n// Supports styles as an object\nconst header = html`\u003cheader style=${{width: '100px', height: '100px'}} /\u003e`;\n\n// Supports styles as a string\nconst em = html`\u003cem style=${'color: red; text-decoration: underline red;'} /\u003e`;\n\n// Supports functions for setting child nodes\nconst header = html`\u003cheader\u003e${(parentElement) =\u003e html`\u003ch1\u003eTitle\u003c/h1\u003e`}\u003c/header\u003e`;\n\n// Supports functions for setting attributes (except event listeners)\nconst footer = html`\u003cfooter class=${(element, attributeName) =\u003e 'foo'}\u003e\u003c/footer\u003e`;\n\n// Supports event listeners (indicated by a prefix of \"on\")\nconst button = html`\u003cbutton onclick=${(e) =\u003e console.log('clicked!')}\u003eClick Me\u003c/button\u003e`;\n```\n\n#### Bindings\n\nWhen a reactive store is interpolated into a DOM element created with `html`, it creates a reactive binding that will automatically update that portion of the DOM, and only that portion, when the internal store value changes:\n\n```javascript\nimport { html, store } from '@ryanmorr/reflex';\n\nconst name = store('John');\n\n// Interpolate a store into an element\nconst element = html`\u003cdiv\u003eMy name is ${name}\u003c/div\u003e`;\n\n// The store value is appended as a text node\nelement.textContent; //=\u003e \"My name is John\"\n\n// The store is bound to the text node, changing\n// the store value automatically updates the text\n// node and only that text node\nname.set('Jim');\n\n// After rendering is completed\nelement.textContent; //=\u003e \"My name is Jim\"\n```\n\nSimilarly to stores, promises can also be interpolated into a DOM element created with `html`, setting the value of the node/attribute when the promise resolves:\n\n```javascript\nimport { html } from '@ryanmorr/reflex';\n\nconst promise = Promise.resolve('World');\n\n// Interpolate a promise like anything else\nconst element = html`\u003cdiv\u003eHello ${promise}\u003c/div\u003e`;\n\n// After the promise resolves and rendering is completed\nelement.textContent; //=\u003e \"Hello World\"\n```\n\n#### Components\n\nFunctional components are also supported. Since reflex is not virtual DOM, a component is only executed once, making both stateless and stateful components easy:\n\n```javascript\nimport { html, store } from '@ryanmorr/reflex';\n\n// A simple component to wrap a common pattern with props and child nodes\nconst Stateless = ({id, children}) =\u003e {\n    return html`\u003csection id=${id}\u003e${children}\u003c/section\u003e`;\n};\n\n// Create the component and return a DOM element\nconst section = html`\u003c${Stateless} id=\"foo\"\u003ebar\u003c//\u003e`;\n\n// A component that holds state\nconst Stateful = () =\u003e {\n    const getTime = () =\u003e new Date().toLocaleTimeString();\n    const time = store(getTime());\n    setInterval(() =\u003e time.set(getTime()), 1000);\n    return html`\u003cdiv\u003eTime: ${time}\u003c/div\u003e`;\n};\n\n// Create the stateful component just like a stateless one\nconst div = html`\u003c${Stateful} /\u003e`;\n```\n\nIf the component function defines an extra parameter as part of its signature, it is provided a function for registering callbacks to be invoked when the component is mounted to the DOM. Optionally, the mount callback can return a cleanup function that is executed when the component is disposed:\n\n```javascript\nimport { html } from '@ryanmorr/reflex';\n\nconst Component = (props, mount) =\u003e {\n\n    mount((element) =\u003e {\n        // Executed when the component is appended to \n        // the DOM and is provided the root element(s)\n\n        return () =\u003e {\n            // Executed when the component is disposed\n        };\n    });\n\n    return html`\u003cdiv\u003e\u003c/div\u003e`;\n};\n```\n\n#### Refs\n\nWhen creating elements with `html`, the `ref` attribute can be used to invoke a function when the element is first created. This is useful for initializing elements and collecting references to deeply nested elements:\n\n```javascript\nimport { html } from '@ryanmorr/reflex';\n\nconst element = html`\u003cdiv ref=${el =\u003e /* initialize element */}\u003e\u003c/div\u003e`;\n```\n\nAdditionally, assigning a store as the value of a `ref` attribute will add the element to an internal array within the store. Subscribers of the store will be notified when elements are added and removed:\n\n```javascript\nimport { html, store, dispose } from '@ryanmorr/reflex';\n\n// Use a store to group multiple element references\nconst foo = store();\nconst element = html`\n    \u003cul\u003e\n        \u003cli ref=${foo}\u003e\u003c/li\u003e\n        \u003cli ref=${foo}\u003e\u003c/li\u003e\n        \u003cli ref=${foo}\u003e\u003c/li\u003e\n        \u003cli ref=${foo}\u003e\u003c/li\u003e\n    \u003c/ul\u003e\n`;\n\n// Returns an array of all elements in the store\nconst elements = foo.value();\n\n// Subscribe to be called when elements are added or removed\nfoo.subscribe((nextElements, prevElements) =\u003e {\n    // Do something\n});\n\n// Disposing an element will automatically remove it from the store\ndispose(element.lastChild);\n```\n\n------\n\n### `effect(...stores?, callback)`\n\nCreate a side effect that is executed every time the DOM has been updated and return a function to stop future calls:\n\n```javascript\nimport { effect } from '@ryanmorr/reflex';\n\nconst stop = effect(() =\u003e {\n    // DOM has been updated\n});\n```\n\nProviding one or more dependencies will create a side effect that is guaranteed to execute after a store value changes and any portion of the DOM that depends on that store has been updated:\n\n```javascript\nimport { effect, store } from '@ryanmorr/reflex';\n\nconst id = store('foo');\nconst content = store('bar');\n\nconst stop = effect(id, content, (idVal, contentVal) =\u003e {\n    // Invoked anytime `id` or `content` changes and the DOM has been updated\n});\n```\n\n------\n\n### `bind(store)`\n\nCreate a two-way binding between a store and a form field, allowing the store to be automatically updated with the current value of the form element when the user changes it, and vice-versa. It supports inputs, checkboxes, radio buttons, selects, and textareas:\n\n```javascript\nimport { bind, html, store } from '@ryanmorr/reflex';\n\nconst value = store('foo');\nconst element = html`\u003cinput value=${bind(value)} /\u003e`;\n```\n\nAlternatively, `bind` can be used to support stores as event listeners:\n\n```javascript\nimport { bind, html, store } from '@ryanmorr/reflex';\n\nconst clicked = store();\nconst button = html`\u003cbutton onclick=${bind(clicked)}\u003eClick Me\u003c/button\u003e`;\nclicked.subscribe((event) =\u003e console.log('clicked'));\n```\n\n------\n\n### `each(store, callback, fallback?)`\n\nEfficiently diffs and renders lists when provided a reactive store that encapsulates an iterable value. Upon reconciliation, the `each` function uses a strict equality operator (`===`) to compare the indexed values of the iterable and determine if an element has been removed or relocated:\n\n```javascript\nimport { each, html, store } from '@ryanmorr/reflex';\n\nconst items = store([1, 2, 3, 4, 5]);\n\nconst element = html`\n    \u003cul\u003e\n        ${each(items, (item, index, array) =\u003e html`\u003cli\u003e${index + 1}: ${item}\u003c/li\u003e`)}\n    \u003c/ul\u003e\n`;\n```\n\nProvide a fallback function as an optional third argument to render content when the store contains an empty iterable or non-iterable value:\n\n```javascript\nimport { each, html, store } from '@ryanmorr/reflex';\n\nconst items = store([]);\n\nconst element = html`\n    \u003csection\u003e\n        ${each(items, \n            (item) =\u003e html`\u003cdiv\u003e${item}\u003c/div\u003e`,\n            () =\u003e html`\u003cdiv\u003eNo Results\u003c/div\u003e`\n        )}\n    \u003c/section\u003e\n`;\n```\n\n------\n\n### `tick()`\n\nReflex uses deferred rendering to batch DOM updates. The `tick` function returns a promise that is resolved when all previously queued DOM updates have been rendered:\n\n```javascript\nimport { tick } from '@ryanmorr/reflex';\n\n// Embed a store in the DOM\nconst store = store('foo');\nconst element = html`\u003cdiv\u003e${store}\u003c/div\u003e`;\n\n// Change a store value to trigger a re-render\nstore.set('bar');\n\n// The DOM is up-to-date when the `tick` promise resolves\nawait tick();\n```\n\n------\n\n### `cleanup(element, callback)`\n\nRegister a callback function to be invoked when an element is disposed. An element is disposed implicitly only during an `each` DOM reconciliation or explicitly when the `dispose` function is called on the element or an ancestor element:\n\n```javascript\nimport { cleanup } from '@ryanmorr/reflex';\n\ncleanup(element, () =\u003e console.log('element and child nodes disposed'));\n```\n\n------\n\n### `dispose(element)`\n\nDestroy all node-store bindings to prevent future DOM updates and invoke any registered `cleanup` functions for an element and its descendants. It will also remove the element and its descendants from any store it was added to via the `ref` attribute:\n\n```javascript\nimport { dispose, store, html, tick } from '@ryanmorr/reflex';\n\n// Create an element-store binding\nconst foo = store('foo');\nconst element = html`\u003cdiv\u003e${foo}\u003c/div\u003e`;\n\n// Update element\nfoo.set('bar');\nawait tick();\nconsole.log(element.textContent); //=\u003e \"bar\"\n\n// Destroy the element-store binding\ndispose(element);\n\n// The element is no longer updated\nfoo.set('baz');\nawait tick();\nconsole.log(element.textContent); //=\u003e \"bar\"\n```\n\n## CSS\n\nFor a CSS-in-JS solution, refer to [fusion](https://github.com/ryanmorr/fusion), a similar library that brings reactivity to CSS variables, media queries, keyframes, and element queries among other helpers. It is also 100% compatible with reflex.\n\n## License\n\nThis project is dedicated to the public domain as described by the [Unlicense](http://unlicense.org/).\n\n[project-url]: https://github.com/ryanmorr/reflex\n[version-image]: https://img.shields.io/github/package-json/v/ryanmorr/reflex?color=blue\u0026style=flat-square\n[build-url]: https://github.com/ryanmorr/reflex/actions\n[build-image]: https://img.shields.io/github/actions/workflow/status/ryanmorr/reflex/node.js.yml?style=flat-square\n[license-image]: https://img.shields.io/github/license/ryanmorr/reflex?color=blue\u0026style=flat-square\n[license-url]: UNLICENSE\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanmorr%2Freflex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fryanmorr%2Freflex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanmorr%2Freflex/lists"}