{"id":21186593,"url":"https://github.com/meefik/neux","last_synced_at":"2026-03-17T18:03:51.243Z","repository":{"id":176739065,"uuid":"654093326","full_name":"meefik/neux","owner":"meefik","description":"A lightweight frontend library for building dynamic user interfaces using declarative element definitions and reactive signals to modify them","archived":false,"fork":false,"pushed_at":"2025-12-27T23:19:53.000Z","size":410,"stargazers_count":11,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-12-29T08:10:50.677Z","etag":null,"topics":["framework","frontend","neux","ui","ux"],"latest_commit_sha":null,"homepage":"https://neux.dev","language":"JavaScript","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/meefik.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":"2023-06-15T11:24:05.000Z","updated_at":"2025-12-27T23:19:56.000Z","dependencies_parsed_at":"2025-08-02T20:06:18.038Z","dependency_job_id":null,"html_url":"https://github.com/meefik/neux","commit_stats":null,"previous_names":["meefik/veux","neux-dev/neux","meefik/neux"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/meefik/neux","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meefik%2Fneux","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meefik%2Fneux/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meefik%2Fneux/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meefik%2Fneux/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/meefik","download_url":"https://codeload.github.com/meefik/neux/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meefik%2Fneux/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30628405,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-17T17:32:55.572Z","status":"ssl_error","status_checked_at":"2026-03-17T17:32:38.732Z","response_time":56,"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":["framework","frontend","neux","ui","ux"],"created_at":"2024-11-20T18:24:35.389Z","updated_at":"2026-03-17T18:03:51.236Z","avatar_url":"https://github.com/meefik.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `{\\}` NEUX\n\n[NEUX](https://github.com/meefik/neux \"Native Extended User eXperience\") is a lightweight frontend library for building dynamic user interfaces using declarative element definitions and reactive signals to modify them. It leverages native JavaScript and browser APIs to minimize boilerplate, making it ideal for creating single page applications (SPA) and custom web components.\n\n**Key features:**\n\n- No JSX, no compiler, just in real-time.\n- Framework-agnostic, use any part of the library independently.\n- Declarative element definitions using plain objects powered by reactive state management.\n- Intuitive two-way reactivity with direct DOM changes without virtual DOM.\n- Built-in localization support for dynamic language adaptation.\n- Easy integration with CSS modules, Tailwind CSS, and other styling solutions.\n- Minimal bundle size (~4kb gzipped) for fast loading.\n- Open source and available under the MIT license.\n\n## Content\n\n1. [Getting Started](#getting-started)\n2. [Rendering Elements](#rendering-elements)\n3. [Reactive Signals](#reactive-signals)\n4. [Localization](#localization)\n5. [Custom Context](#custom-context)\n6. [Simple Routing](#simple-routing)\n7. [Building with Vite](#building-with-vite)\n8. [Using with Tailwind CSS](#using-with-tailwind-css)\n9. [Using with daisyUI](#using-with-daisyui)\n10. [Using with Web Components](#using-with-web-components)\n11. [Creating your own Web Component](#creating-your-own-web-component)\n12. [Code Example](#code-example)\n\n## Getting Started\n\nGetting started with NEUX is quick and effortless. You can include NEUX directly in your project without any additional build steps. However, you can use the library with bundlers like Vite if it needed.\n\nTo use NEUX in the browser, simply add the following to your HTML page:\n\n```html\n\u003cscript src=\"https://unpkg.com/neux\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  // Import NUEX functions\n  const { render, mount, signal, effect, l10n } = window.neux;\n  // Start building your app right away!\n\u003c/script\u003e\n```\n\nOr you can import it as an ES module:\n\n```html\n\u003cscript type=\"module\"\u003e\n  // Import NUEX functions\n  import { render, mount, signal, effect, l10n } from 'https://esm.sh/neux';\n  // Start building your app right away!\n\u003c/script\u003e\n```\n\nTake a look at the example below. It creates a button that displays a counter. Every time the button is clicked, the count is incremented and the displayed text is automatically updated via NEUX's reactive state management.\n\n```js\n// Create reactive state\nconst state = signal({ count: 1 });\n// Render button element\nconst el = render({\n  // Tag name\n  tag: 'button',\n  // Event listeners\n  on: {\n    // Increment count on click\n    click: () =\u003e state.count++,\n  },\n  // Dynamic text content ($ mark enables reactivity)\n  children: () =\u003e `Count: ${state.$count}`,\n});\n// Mount to DOM\nmount(el, document.body);\n```\n\nNEUX also supports a concise [HyperScript](https://github.com/hyperhype/hyperscript)-like syntax for element creation using the `render()` function, similar to approaches found in other libraries. This syntax helps to define your elements in a more functional manner.\n\n```js\n// Create reactive state\nconst state = signal({ count: 1 });\n// Render button element\nconst el = render(\n  // Tag name\n  'button',\n  {\n    // Event listeners\n    on: {\n      // Increment count on click\n      click: () =\u003e state.count++,\n    },\n  },\n  // Dynamic text content ($ mark enables reactivity)\n  () =\u003e `Count: ${state.$count}`,\n);\n// Mount to DOM\nmount(el, document.body);\n```\n\n## Rendering Elements\n\nNEUX provides a powerful way to declaratively define HTML elements using plain JavaScript objects. You can specify various properties such as tag name, attributes, styles, event listeners, children, and other native HTML properties. The `render()` function processes these definitions and creates the corresponding HTML elements. The created elements can then be mounted to the DOM using the `mount()` function.\n\nYou should use the `render()` function to create an `Element` or `DocumentFragment` by declarative definition. Below is an overview of the most common parameters available for element configuration:\n\n- `tag`: (String or Element) Specifies the HTML tag name (e.g., \"div\", \"span\") or HTML markup to create or an existing Element to use directly.\n- `classList`: (Array of Strings or Function) Specifies one or more CSS classes to add to the element. It can be a static array or a function that returns an array based on dynamic context.\n- `attributes`: (Object or Function) Maps attribute names to their corresponding values. Use a static object for fixed attributes or a function for dynamic assignment.\n- `style`: (Object or Function) Sets inline CSS styles via an object where keys are CSS property names. This can also be defined as a function to handle dynamic styling.\n- `dataset`: (Object or Function) Assigns custom data attributes (data-*) through a static mapping or a function that returns the mapping.\n- `on`: (Object) Adds event listeners to the element. Each key represents an event name (e.g., \"click\", \"change\") with its corresponding handler function.\n- `children`: (String, Array of Elements, or Function) Defines the inner content of the element. This can be a direct string, an array of element definitions, or a function that returns child nodes for dynamic rendering.\n- `ref`: (Function) A callback that receives the created element, allowing you to store a reference or perform additional operations immediately after creation.\n\nExtra configuration for edge cases:\n\n- `shadowRootMode`: (String) Defines the mode of the element’s [shadow DOM](https://developer.mozilla.org/docs/Web/API/Web_components/Using_shadow_DOM), determining its accessibility and encapsulation. Options include 'open' (the shadow root is accessible via the element’s shadowRoot property) and 'closed' (the shadow root is hidden, preventing external access).\n- `adoptedStyleSheets`: (Array) Specifies one or more [CSSStyleSheet](https://developer.mozilla.org/docs/Web/API/CSSStyleSheet) objects that can be associated with the element’s shadow DOM. This enables the use of constructable stylesheets for efficient, reusable styling.\n- `namespaceURI`: (String) Specifies the XML namespace URI when [creating namespaced elements](https://developer.mozilla.org/docs/Web/API/Document/createElementNS), such as SVG or MathML. Usually, this property is not required because it is automatically determined by the tag name.\n\nYou can also include any other parameters specific to particular elements. This flexible approach supports both static configurations and dynamic, reactive user interfaces.\n\n```js\nconst el = render({\n  tag: 'ul',\n  classList: ['list'],\n  ref: ref =\u003e {\n    console.log(ref);\n  },\n  children: ['Item 1', 'Item 2']\n    .map((item, index) =\u003e {\n      return {\n        tag: 'li',\n        style: {\n          color: 'red',\n        },\n        attributes: {\n          title: item,\n        },\n        dataset: {\n          index,\n        },\n        textContent: item,\n      };\n    }),\n});\n```\n\nThe `el` variable will contain an HTML element with the following markup:\n\n```html\n\u003cul class=\"list\"\u003e\n  \u003cli title=\"Item 1\" data-index=\"0\" style=\"color: red;\"\u003eItem 1\u003c/li\u003e\n  \u003cli title=\"Item 2\" data-index=\"1\" style=\"color: red;\"\u003eItem 2\u003c/li\u003e\n\u003c/ul\u003e\n```\n\nTo attach any HTML element to the DOM you should use the `mount()` function. This function attaches elements to the DOM and sets up a [MutationObserver](https://developer.mozilla.org/docs/Web/API/MutationObserver) on the target to dispatch custom events on lifecycle changes. These events are emitted for each element in the target DOM tree.\n\nList of lifecycle events:\n\n- `mounted` is fired when the element is added to the DOM.\n- `updated` is fired when the element property is updated.\n- `changed` is fired when the element attribute is changed by external sources.\n- `removed` is fired when the element is removed from the DOM.\n\nThe `removed` event is used internally to clean up signal bindings. You can prevent the default behavior for the target element and all its children by calling the `preventDefault()` method.\n\nExample of using lifecycle events:\n\n```js\n// Create an HTML element\nconst el = render({\n  // Event listeners\n  on: {\n    mounted(e) {\n      console.log('Element mounted:', e);\n    },\n    changed(e) {\n      console.log('Element changed:', e);\n    },\n    removed(e) {\n      // you can prevent the default behavior\n      // e.preventDefault();\n      console.log('Element removed:', e);\n    },\n  },\n  textContent: 'Hello World!',\n});\n// Mount to DOM and set up lifecycle events\nmount(el, document.body);\n// Change the element attribute\nel.setAttribute('title', 'Text');\n// Remove the element fomr DOM\nel.remove();\n```\n\nIn the `mount()` function, the second argument can be a target HTML element or CSS selector that will be used to find the target.\n\nYou can include any SVG icon as HTML markup and change its styles (size, color) via the `classList` or `attributes` parameters (raw import works with Vite):\n\n```js\nimport githubIcon from '@svg-icons/fa-brands/github.svg?raw';\n\nconst svgElement = render({\n  tag: githubIcon,\n  classList: ['icon'],\n  attributes: {\n    width: '64px',\n    height: '64px'\n  }\n});\n```\n\nAdditionally, you can create a [DocumentFragment](https://developer.mozilla.org/docs/Web/API/DocumentFragment) by simply passing an array to the `render()` function:\n\n```js\n// Create DocumentFragment\nconst fragment = render([\n  { tag: 'span', textContent: 'Item 1' },\n  { tag: 'span', textContent: 'Item 2' },\n  { tag: 'span', textContent: 'Item 3' },\n]);\n// Mount to DOM\nmount(fragment, document.body);\n```\n\nProbably you want to change the element properties dynamically. NEUX allows you to use functions for most of the element parameters. These functions are reactive and will be re-evaluated by specific triggers such as `refresh` event or signals.\n\nLook at the example below:\n\n```js\nconst list = [\n  { text: 'Item 1' },\n  { text: 'Item 2' },\n];\nconst el = render({\n  tag: 'ul',\n  children: () =\u003e {\n    return list.map(item =\u003e {\n      return {\n        tag: 'li',\n        textContent: () =\u003e item.text,\n      };\n    });\n  },\n});\nmount(el, document.body);\n```\n\nIn this example, the `children` parameter is defined as a function that returns an array of list items. Each list item has its `textContent` defined as a function that retrieves the `text` property from the corresponding item in the `list` array.\n\nYou can even use asynchronous functions to fetch data or perform other asynchronous operations before rendering the element properties:\n\n```js\nconst el = render({\n  children: async () =\u003e {\n    const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');\n    const data = await response.json();\n    return [{\n      tag: 'h3',\n      textContent: data.title,\n    }, {\n      tag: 'p',\n      textContent: data.body,\n    }]\n  },\n});\nmount(el, document.body);\n```\n\nWhen you want to update the list or change the text of an item, you can modify the `list` array or its items directly. To trigger a re-evaluation of the functions and update the DOM accordingly, you can dispatch a custom `refresh` event to the target element:\n\n```js\n// Add a new item to the list\nlist.push({ text: 'Item 3' });\n// Re-render the entire element\nel.dispatchEvent(new CustomEvent('refresh'));\n// Update the text of the first item\nlist[0].text = 'Updated Item 1';\n// Update only specific properties\nel.children[0].dispatchEvent(new CustomEvent('refresh', { detail: ['textContent'] }));\n```\n\nNote that when dispatching the `refresh` event, you can optionally provide a `detail` array that specifies which properties should be updated. If no detail is provided, all reactive functions will be re-evaluated on the target element. It's good to know that only the changed elements are replaced when lists like `children` are updated.\n\nInstead of using `refresh` events, you can also use reactive signals to manage state and automatically update the DOM when the state changes. This approach is more efficient and easier to maintain, as it eliminates the need for manual event dispatching.\n\n## Reactive Signals\n\nSignals in NEUX are reactive proxies for objects. They track changes automatically and update any linked views or computed fields. Use signals to create reactive state, derived values, and listeners for side effects or debugging.\n\nFor example:\n\n```js\n// Reactive state with fields, computed properties, and listeners\nconst state = signal({\n  count: 1,\n  multiplier: 2,\n  list: [\n    { text: 'Item 1' },\n    { text: 'Item 2', checked: true },\n  ],\n  // computed field\n  double: obj =\u003e obj.$count * 2,\n  // computed field that tracks all changes, including nested objects\n  filtered: obj =\u003e obj.list.$$.filter(item =\u003e item.checked),\n});\n// Update the computed field\nstate.double = obj =\u003e state.$count * state.$multiplier;\n// Modify fields\nstate.count++;\nstate.list.push({ text: 'Item 3' });\n// Remove the field and its related reactive effects\ndelete state.double;\n```\n\nIn computed fields, prefixing a property name with `$` marks it as reactive. When the property's value changes, the computed function is automatically invoked with its new value.\n\n**ATTENTION**\n- Removing or replacing the observed object/array will break all bindings.\n- Only the fields accessed during the initial synchronous execution are tracked for updates.\n\nYou can use the `$$` sign to subscribe to any changes in this object, array, or any of its nested objects. Alternatively, use the `$` sign to track changes in the object or array without tracking changes in nested objects:\n\n```js\n// Reactive list of items\nconst list = signal([\n  { id: 1, text: 'Item 1' },\n  { id: 2, text: 'Item 2' },\n]);\n// Create an HTML element\nconst el = render({\n  tag: 'ul',\n  children: () =\u003e {\n    // Track changes in the list array using `$` sign,\n    // such as adding, replacing, or deleting items, \n    // except for nested objects\n    return list.$.map((item) =\u003e {\n      return {\n        tag: 'li',\n        dataset: {\n          id: item.id,\n        },\n        // Track changes the specific field\n        textContent: () =\u003e item.$text,\n      };\n    });\n  },\n});\n// Mount to DOM\nmount(el, document.body);\n// Add new item to the array and then re-render the list\nlist.push({ id: 3, text: 'Item 3' });\n// Replace the existing item with a new one that has the same values\n// you should change the `id` to a unique value to force rerendering\nlist.splice(1, 1, { id: 4, text: 'Item 2' });\n// Change the text content of the `li` element without replacing the element\nlist[1].text = 'Item 2 was changed';\n```\n\nYou may encounter a problem when trying to replace an array item with a new object that contains the same values. The NEUX calculates DOM changes by value. In this case, the element will not be replaced, even if the object in the state is replaced. This behavior may lead to unexpected results when using reactive properties on child elements based on array items. To solve this problem, add a unique identifier to each array item and use it as a data attribute key for each element. \n\nYou can creates a reactive effect that computes a derived value and triggers a side effect.\n\nFor example:\n\n```js\nconst dispose = effect(\n  // Reactive getter: get count from state and subscribe to changes with '$' marker\n  () =\u003e {\n    const { $count } = state;\n    return $count * 2;\n  },\n  // Non-reactive setter: get result from getter and use it\n  (value) =\u003e {\n    console.log(`The doubled count is: ${value}`);\n  },\n);\n// Stop tracking changes and clear all associated subscriptions\ndispose();\n```\n\nThe first function (getter) retrieves `$count` from the reactive state and returns its multiplied value (in this case, doubled). This ensures that any change in `$count` will automatically update the computed result.\n\nThe second function (setter) acts as a non-reactive callback that receives the computed value and performs an action, such as logging it to the console.\n\nOptionally, all reactivity subscriptions set up by the effect can be cleared by invoking the `dispose()` function, which stops further tracking and updates.\n\nAdditionally, you can subscribe to changes in your reactive state using dedicated listener methods. These listeners help you capture when a property's value is added, updated, or deleted. In the example below, the handler function demonstrates how to log the new value, old value, property name, the changed object, and any additional nested fields that were affected.\n\nHere’s the example:\n\n```js\n// Define a handler function that receives state change details\nconst handler = (newv, oldv, prop, obj, nested) =\u003e {\n  console.log('New value:', newv);\n  console.log('Old value:', oldv);\n  console.log('Changed property:', prop);\n  console.log('Reactive object:', obj);\n  console.log('Nested fields (if any):', nested);\n\n  // Determine if the property was added, updated, or deleted\n  if (newv === undefined) {\n    console.log('Property deleted');\n  } else if (oldv === undefined) {\n    console.log('Property added');\n  } else {\n    console.log('Property updated');\n  }\n};\n// Subscribe to changes on the 'double' property\nstate.$$on('double', handler);\n// Subscribe with a one-time listener for the 'double' property\nstate.$$once('double', handler);\n// Unsubscribe a specific listener from the 'double' property\nstate.$$off('double', handler);\n// Remove all listeners for the 'double' property\nstate.$$off('double');\n// Subscribe to any changes on this object\nstate.$$on('#', handler);\n// Subscribe to any changes on this object and all nested children\nstate.$$on('*', handler);\n```\n\nIn this example:\n- The handler function logs useful details about state changes.\n- Using `$$on()`, you can add persistent listeners.\n- With `$$once()`, the listener triggers only the first time the change occurs.\n- The `$$off()` method allows you to remove specific or all listeners for a given property.\n- The hash `'#'` subscribes the handler to any changes on this object.\n- The wildcard `'*'` subscribes the handler to any changes on this object and all nested children.\n\nThis flexibility lets you efficiently track and respond to state mutations across your application.\n\n## Localization\n\nLocalization is used to display the application interface in different languages.You can use localized number and date formatting with [Intl.NumberFormat](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) and [Intl.DateTimeFormat](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat).\n\nTranslation example:\n\n```js\nconst t = l10n({\n  en: {\n    say: {\n      hello: \"Hello %{name}!\"\n    },\n    number: 'number: %{val}',\n    date: 'date: %{val}'\n  },\n  ru: {\n    say: {\n      hello: \"Привет %{name}!\"\n    },\n    number: 'число: %{val}',\n    date: 'дата: %{val}'\n  }\n}, {\n  language: navigator.language,\n  fallback: 'en'\n});\n\nconst msgEn = t('say.hello', { name: 'World' });\nconsole.log(msgEn); // Hello World!\n\nconst numberMsg = t('number', {\n  val: [12345, {\n    style: 'currency',\n    currency: 'USD'\n  }]\n});\nconsole.log(numberMsg); // number: $12,345.00\n\nconst dateMsg = t('date', {\n  val: [new Date('2025-01-15'), {\n    weekday: 'long',\n    year: 'numeric',\n    month: 'long',\n    day: 'numeric'\n  }]\n});\nconsole.log(dateMsg); // date: Wednesday, January 15, 2025\n\nconst msgRu = t('say.hello', { name: 'Мир' }, 'ru');\nconsole.log(msgRu); // Привет Мир!\n```\n\n## Custom Context\n\nBy default, NEUX uses a global context for the `signal()` and `render()` functions. However, there are scenarios where you might need to use a custom context for signals and rendering. This allows you to separate multiple states, ensuring that reactivity works only within the same context. You can create an object and bind it to these functions.\n\nHere’s an example of how to use a custom context:\n\n```js\n// Custom context\nconst context = { hi: 'hello' };\n// Signal with custom context\nconst state = signal.call(context, {\n  count() {\n    console.log('signal', this.hi); // hello\n    return 1;\n  }\n});\n// Render with the same context\nconst el = render.call(context, {\n  textContent() {\n    console.log('render', this.hi); // hello\n    return state.$count;\n  }\n});\n// Mount to DOM\nmount(el, document.body);\n```\n\nIn this example:\n- A custom context object is created with a property `hi`.\n- The `signal` function is called with the custom context using `signal.call(context, {...})`.\n- The `render` function is also called with the same custom context using `render.call(context, {...})`.\n- The `this` keyword inside the signal and render functions refers to the custom context, allowing access to its properties.\n\nThis approach ensures that the reactivity and rendering logic are scoped to the custom context, providing better modularity and separation of concerns in your application or within Web Components.\n\n## Simple Routing\n\nNEUX lets you implement routing simply with reactive state. By tracking the URL hash, you can switch between views dynamically. The following example demonstrates a basic routing setup with detailed comments and improved styling.\n\n```js\n// Initialize routing state\nconst state = signal({\n  path: location.hash.slice(1) || 'Home',\n});\n// Route components\nconst Home = () =\u003e ({\n  tag: 'div',\n  textContent: 'Welcome to the Home Page!',\n});\nconst About = () =\u003e ({\n  tag: 'div',\n  textContent: 'This is the About Page.',\n});\nconst NotFound = () =\u003e ({\n  tag: 'div',\n  textContent: '404 - Page Not Found',\n});\n// Route views\nconst views = { Home, About };\n// App layout with navigation and content\nconst el = render({\n  children: [\n    // Navigation links\n    {\n      tag: 'nav',\n      children: [{\n        tag: 'a',\n        href: '#Home',\n        textContent: 'Home',\n      }, {\n        tag: 'a',\n        href: '#About',\n        textContent: 'About',\n      }, {\n        tag: 'a',\n        href: '#Blog',\n        textContent: 'Blog',\n      }],\n    },\n    // Main content\n    {\n      tag: 'main',\n      children: () =\u003e {\n        const View = views[state.$path];\n        return View ? View() : NotFound();\n      },\n    },\n  ],\n});\n// Update state on hash change\nwindow.addEventListener('hashchange', () =\u003e {\n  state.path = location.hash.slice(1);\n});\n// Mount to DOM\nmount(el, document.body);\n```\n\nIn this setup:\n- The reactive state holds the current path.\n- Navigation links update the URL hash, which triggers a state change.\n- The main content area dynamically renders the corresponding view.\n- If the route is not found, a default \"Not Found\" view is displayed.\n\n## Building with Vite\n\nYou can use NEUX with [Vite](https://vitejs.dev) bundler.\n\nHow to set up:\n\n**1.** Create a new Vite project:\n\n```sh\nnpm init vite@latest -- --template vanilla\n```\n\n**2.** Install the `neux` module:\n\n```sh\nnpm install --save-dev neux\n```\n\n**3.** Paste your application code into the `src/main.js` file:\n\n```js\nimport { render, mount } from 'neux';\n\nconst el = render({\n  textContent: 'Hello World!',\n});\n\nmount(el, '#app');\n```\n\n**4.** Run the project:\n\n```sh\nnpm run dev\n```\n\n## Using with Tailwind CSS\n\nIt also fits well with [Tailwind CSS](https://tailwindcss.com). After [installing Tailwind CSS](https://tailwindcss.com/docs/installation) into your project you can use CSS classes in the `classList` field as `String` or `Array`.\n\nHow to set up your Vite project:\n\n**1.** Install the required modules:\n\n```sh\nnpm install --save-dev tailwindcss @tailwindcss/vite\n```\n\n**2.** Create the file `vite.config.js`:\n\n```js\nimport { defineConfig } from 'vite';\nimport tailwindcss from '@tailwindcss/vite';\n\nexport default defineConfig({\n  plugins: [\n    tailwindcss(),\n  ],\n});\n```\n\n**3.** Replace the contents of the `src/style.css` file with:\n\n```css\n@import \"tailwindcss\";\n```\n\n**4.** Replace the contents of the `src/main.js` file with the example:\n\n```js\nimport './style.css';\nimport { render, mount } from 'neux';\n\nconst el = render({\n  tag: 'h1',\n  classList: ['text-3xl', 'font-bold', 'underline'],\n  textContent: 'Hello world!',\n});\n\nmount(el, '#app');\n```\n\n## Using with daisyUI\n\nTo simplify styles you can use [daisyUI](https://daisyui.com). This is a popular component library for [Tailwind CSS](https://tailwindcss.com).\n\nHow to set up your Tailwind CSS project:\n\n**1.** Install the required modules:\n\n```sh\nnpm install --save-dev daisyui\n```\n\n**2.** Replace the contents of the `src/style.css` file:\n\n```css\n@plugin \"daisyui\";\n```\n\n**3.** Replace the contents of the `src/main.js` file with the example:\n\n```js\nimport './style.css';\nimport { signal, render, mount } from 'neux';\n\nconst state = signal({ count: 0 });\n\nconst el = render({\n  classList: ['container', 'm-auto', 'p-8', 'flex', 'gap-4'],\n  children: [{\n    tag: 'button',\n    classList: ['btn', 'btn-primary'],\n    textContent: '-1',\n    on: {\n      click: () =\u003e {\n        state.count--;\n      },\n    },\n  }, {\n    tag: 'input',\n    type: 'number',\n    classList: ['input', 'input-bordered', 'w-full'],\n    value: () =\u003e state.$count,\n    on: {\n      change: ({ target }) =\u003e {\n        state.count = parseInt(target.value);\n      },\n    },\n  }, {\n    tag: 'button',\n    classList: ['btn', 'btn-primary'],\n    textContent: '+1',\n    on: {\n      click: () =\u003e state.count++,\n    },\n  }],\n});\n\nmount(el, '#app');\n```\n\n## Using with Web Components\n\nYou can use NEUX along with any [Web Components](https://developer.mozilla.org/docs/Web/API/Web_Components). Many component libraries can be [found here](https://open-wc.org/guides/community/component-libraries/).\n\nLet's take an example of working with the [BlueprintUI](https://blueprintui.dev) library:\n\n**1.** Install the required modules:\n\n```sh\nnpm install --save-dev @blueprintui/components @blueprintui/themes @blueprintui/layout @blueprintui/typography\n```\n\n**2.** Import styles in the `src/style.css` file:\n\n```css\n@import '@blueprintui/layout/index.min.css';\n@import '@blueprintui/typography/index.min.css';\n@import '@blueprintui/themes/index.min.css';\n```\n\n**3.** Replace the contents of the `src/main.js` file with the example:\n\n```js\nimport './style.css';\nimport '@blueprintui/components/include/button.js';\nimport '@blueprintui/components/include/card.js';\nimport '@blueprintui/components/include/input.js';\nimport { render, mount } from 'neux';\n\nconst el = render({\n  tag: 'bp-card',\n  children: [{\n    tag: 'h2',\n    slot: 'header',\n    attributes: {\n      'bg-text': 'section',\n    },\n    textContent: 'Heading',\n  }, {\n    tag: 'bp-field',\n    children: [{\n      tag: 'label',\n      textContent: 'label',\n    }, {\n      tag: 'bp-input',\n    }],\n  }, {\n    slot: 'footer',\n    attributes: {\n      'bp-layout': 'inline gap:xs inline:end',\n    },\n    children: [{\n      tag: 'bp-button',\n      attributes: {\n        action: 'secondary',\n      },\n      textContent: 'Cancel',\n    }, {\n      tag: 'bp-button',\n      attributes: {\n        status: 'accent',\n      },\n      textContent: 'Confirm',\n    }],\n  }],\n});\n\nmount(el, document.body);\n```\n\n## Creating your own Web Component\n\nYou can create your own components using [one of the libraries](https://open-wc.org/guides/community/base-libraries/). However, you can also use NEUX to create your own Web Components.\n\nHere is an example of a web component definition:\n\n```js\nimport { signal, render, mount } from 'neux';\n\n// Create a custom web component\nclass Counter extends HTMLElement {\n  // List of attributes to observe for changes\n  static observedAttributes = ['value'];\n  // The component constructor override\n  constructor() {\n    super();\n    const context = {};\n    this.attrs = signal.call(context, {});\n    this.attrs.$$on(\n      this.constructor.observedAttributes,\n      (newv, oldv, prop) =\u003e this.setAttribute(prop, newv),\n    );\n    const el = render.call(context, this.render());\n    const shadowRoot = this.attachShadow({ mode: 'open' });\n    mount(el, shadowRoot);\n  }\n  // Called when an observed attribute is changed\n  attributeChangedCallback(name, oldv, newv) {\n    this.attrs[name] = newv;\n  }\n  // Describe the object to render the component\n  render() {\n    return [{\n      tag: 'input',\n      type: 'number',\n      value: () =\u003e this.attrs.$value,\n      on: {\n        change: (e) =\u003e {\n          this.attrs.value = e.target.value;\n        },\n      },\n    }, {\n      children: [{\n        tag: 'slot',\n        name: 'label',\n      }, {\n        tag: 'span',\n        textContent: () =\u003e this.attrs.$value,\n      }]\n    }];\n  }\n}\n// Define custom element\ncustomElements.define('ne-counter', Counter);\n```\n\nUse this web component:\n\n```js\nconst el = render({\n  tag: 'ne-counter',\n  attributes: {\n    value: 5,\n  },\n  children: [{\n    tag: 'span',\n    slot: 'label',\n    textContent: 'Count: ',\n  }],\n});\n\nmount(el, document.body);\n```\n\n## Code Example\n\nThis example shows how to write a simple app (To-Do List):\n\n```js\n// Create a reactive state\nconst state = signal({\n  // Todo items\n  list: [\n    { text: 'Item 1' },\n    { text: 'Item 2', checked: true },\n    { text: 'Item 3' },\n  ],\n  // List of checked items\n  filtered: (obj) =\u003e {\n    return obj.list.$$.filter(item =\u003e !item.checked);\n  },\n});\n// Create HTML elements\nconst el = render({\n  children: [{\n    tag: 'h1',\n    textContent: 'To Do',\n  }, {\n    tag: 'input',\n    placeholder: 'Enter your task...',\n    autofocus: true,\n    on: {\n      keyup(e) {\n        if (e.key === 'Enter') {\n          e.preventDefault();\n          state.list.push({ text: e.target.value });\n          e.target.value = '';\n        }\n      },\n    },\n  }, {\n    children: [{\n      tag: 'input',\n      type: 'checkbox',\n      on: {\n        change(e) {\n          const checked = e.target.checked;\n          state.list.forEach((item) =\u003e {\n            item.checked = checked;\n          });\n        },\n      },\n    }, {\n      tag: 'label',\n      textContent: 'Mark all as complete',\n    }],\n  }, {\n    tag: 'ul',\n    children: () =\u003e {\n      // Redraw the list partially if any child items are added, replaced, or removed.\n      // Any updates inside nested objects are ignored.\n      return state.list.$.map((item) =\u003e {\n        return {\n          tag: 'li',\n          children: [{\n            tag: 'input',\n            type: 'checkbox',\n            checked: () =\u003e item.$checked,\n            on: {\n              change(e) {\n                item.checked = e.target.checked;\n              },\n            },\n          }, {\n            tag: 'label',\n            style: {\n              textDecoration: () =\u003e item.$checked ? 'line-through' : 'none',\n            },\n            textContent: () =\u003e item.$text,\n          }, {\n            tag: 'button',\n            textContent: 'x',\n            on: {\n              click(e) {\n                e.preventDefault();\n                const index = state.list.indexOf(item);\n                state.list.splice(index, 1);\n              },\n            },\n          }],\n        };\n      });\n    },\n  }, {\n    textContent: () =\u003e {\n      return `Total items: ${state.$filtered.length} / ${state.list.$length}`;\n    },\n  }],\n});\n// Mount to the DOM\nmount(el, document.body);\n```\n\nTry it in the playground:\n\n- [To-Do App](https://v47.livecodes.io/?x=id/uhkch4mgxfp)\n- [15 Puzzle](https://v47.livecodes.io/?x=id/efx9s47xqrr)\n- [Tic-Tac-Toe](https://v47.livecodes.io/?x=id/r4cjbuidtb3)\n- [SVG Clock](https://v47.livecodes.io/?x=id/mr5hs94hd5i)\n- [Sketch](https://v47.livecodes.io/?x=id/q7tistivmkt)\n- [File Tree](https://v47.livecodes.io/?x=id/wfcdxuaqbq9)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmeefik%2Fneux","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmeefik%2Fneux","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmeefik%2Fneux/lists"}