{"id":26307498,"url":"https://github.com/nabeelalihashmi/nthjs","last_synced_at":"2026-05-04T00:32:59.069Z","repository":{"id":282260397,"uuid":"948005930","full_name":"nabeelalihashmi/nthjs","owner":"nabeelalihashmi","description":"Signals Based Minimal Modular JavaScript Reactivity","archived":false,"fork":false,"pushed_at":"2025-03-14T07:27:04.000Z","size":20,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-24T11:47:22.845Z","etag":null,"topics":["blade","blade-template","dom","frontend","javascript","javascript-library","laravel","library","reactive","reactivity","signals"],"latest_commit_sha":null,"homepage":"https://nabeelalihashmi.github.io/nthjs/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nabeelalihashmi.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,"zenodo":null}},"created_at":"2025-03-13T15:38:26.000Z","updated_at":"2025-03-14T07:27:07.000Z","dependencies_parsed_at":"2025-08-20T07:19:50.689Z","dependency_job_id":"19dc5cbc-9654-49c3-85f8-c545124947b8","html_url":"https://github.com/nabeelalihashmi/nthjs","commit_stats":null,"previous_names":["nabeelalihashmi/nthjs"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/nabeelalihashmi/nthjs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nabeelalihashmi%2Fnthjs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nabeelalihashmi%2Fnthjs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nabeelalihashmi%2Fnthjs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nabeelalihashmi%2Fnthjs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nabeelalihashmi","download_url":"https://codeload.github.com/nabeelalihashmi/nthjs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nabeelalihashmi%2Fnthjs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32590276,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T22:12:39.696Z","status":"ssl_error","status_checked_at":"2026-05-03T22:09:10.534Z","response_time":103,"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":["blade","blade-template","dom","frontend","javascript","javascript-library","laravel","library","reactive","reactivity","signals"],"created_at":"2025-03-15T10:15:00.158Z","updated_at":"2026-05-04T00:32:59.051Z","avatar_url":"https://github.com/nabeelalihashmi.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NthJS - Minimal Modular JavaScript Reactivity Starter Kit Library\n\nNthJS is a lightweight, dependency-free javascript reactivity and reactive dom library. It provides fine-grained reactivity without a build step, making it an minimal alternative to frameworks like Vue or Svelte for smaller projects. It is great companion for PHP projects. It is ~1.4kb minified+gzipped.\n\n## Main Idea\nNthJS provides a minimal yet solid base to make your own reactive library according to your needs. Just edit `nthjs.js` file, This is \u003c250 loc. Remove functions/features you don't want or add functions/features you want, such as automatic binding of data attributes etc. \n\n\n## Motivation for NthJS\nI needed simple reactivity many time in frontend, but didn’t want the overhead of a build step. Neither I wanted to use Vue or other those have many features I never needed. I wanted something lightweight, yet powerful - something that took inspiration from Vue and Svelte, including features like watchers and group bindings, without unnecessary bloat. That’s why nthJS is born. It’s minimal, modular, and designed to keep reactivity purely in JavaScript, avoiding extra markup in the DOM. While it focuses on a JS-first approach, it can be easily extended to support data-attribute-based bindings if needed.\n\n## Features\n- **Signals \u0026 Effects**: Reactive state with automatic dependency tracking.\n- **Computed Values**: Derived state that updates when dependencies change.\n- **Watchers**: Listen for state changes and trigger callbacks.\n- **DOM Bindings**: Bind state to the DOM with `createBinding`, `createListBinding`, and `createGroupBinding`.\n- **Async Support**: Fetch data reactively with `createAsyncSignal`.\n\n## Installation\nSimply include the `nthjs.js` file in your project via cdn.\n\n```\n\u003cscript src=\"https://cdn.jsdelivr.net/gh/nabeelalihashmi/nthjs@main/nthjs.min.js\"\u003e\u003c/script\u003e\n```\n\nor download and use.\n\n## Complete Example\n\n```\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n    \u003cmeta charset=\"UTF-8\"\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n    \u003ctitle\u003eNthJS Demo\u003c/title\u003e\n    \u003cstyle\u003e\n        body {\n            font-family: Arial, sans-serif;\n            margin: 20px;\n            background: #f4f4f4;\n            text-align: center;\n        }\n        h1 {\n            color: #333;\n        }\n        .container {\n            background: white;\n            padding: 20px;\n            border-radius: 10px;\n            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);\n            display: inline-block;\n        }\n        .counter {\n            font-size: 24px;\n            margin: 10px 0;\n        }\n        button {\n            padding: 10px 15px;\n            margin: 5px;\n            font-size: 16px;\n            cursor: pointer;\n            border: none;\n            border-radius: 5px;\n            transition: 0.3s;\n        }\n        button:hover {\n            background: #ddd;\n        }\n        ul {\n            list-style: none;\n            padding: 0;\n        }\n        li {\n            padding: 8px;\n            background: #ddd;\n            margin: 5px 0;\n            border-radius: 5px;\n        }\n        .loading {\n            color: orange;\n        }\n        .error {\n            color: red;\n        }\n    \u003c/style\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\n    \u003ch1\u003eNthJS Demo\u003c/h1\u003e\n\n    \u003cdiv class=\"container\"\u003e\n\n        \u003c!-- Counter --\u003e\n        \u003ch2\u003eCounter\u003c/h2\u003e\n        \u003cdiv class=\"counter\" id=\"count\"\u003e0\u003c/div\u003e\n        \u003cbutton id=\"inc\"\u003eIncrement\u003c/button\u003e\n        \u003cbutton id=\"dec\"\u003eDecrement\u003c/button\u003e\n\n        \u003c!-- Input Binding --\u003e\n        \u003ch2\u003eName Input\u003c/h2\u003e\n        \u003cinput type=\"text\" id=\"nameInput\"\u003e\n        \u003cp\u003eHello, \u003cspan id=\"nameDisplay\"\u003e\u003c/span\u003e!\u003c/p\u003e\n\n        \u003c!-- Computed Signal --\u003e\n        \u003ch2\u003eSum of Two Numbers\u003c/h2\u003e\n        \u003cinput type=\"number\" id=\"num1\" value=\"0\"\u003e +\n        \u003cinput type=\"number\" id=\"num2\" value=\"0\"\u003e\n        \u003cp\u003eSum: \u003cspan id=\"sum\"\u003e\u003c/span\u003e\u003c/p\u003e\n\n        \u003c!-- List Rendering --\u003e\n        \u003ch2\u003eDynamic List\u003c/h2\u003e\n        \u003cbutton id=\"addItem\"\u003eAdd Item\u003c/button\u003e\n        \u003cul id=\"list\"\u003e\u003c/ul\u003e\n\n        \u003c!-- Group Binding --\u003e\n        \u003ch2\u003eFavorite Color\u003c/h2\u003e\n        \u003clabel\u003e\u003cinput type=\"radio\" name=\"color\" value=\"red\"\u003e Red\u003c/label\u003e\n        \u003clabel\u003e\u003cinput type=\"radio\" name=\"color\" value=\"blue\"\u003e Blue\u003c/label\u003e\n        \u003clabel\u003e\u003cinput type=\"radio\" name=\"color\" value=\"green\"\u003e Green\u003c/label\u003e\n        \u003cp\u003eSelected Color: \u003cspan id=\"selectedColor\"\u003e\u003c/span\u003e\u003c/p\u003e\n\n        \u003ch2\u003eHobbies\u003c/h2\u003e\n        \u003clabel\u003e\u003cinput type=\"checkbox\" value=\"Reading\"\u003e Reading\u003c/label\u003e\n        \u003clabel\u003e\u003cinput type=\"checkbox\" value=\"Gaming\"\u003e Gaming\u003c/label\u003e\n        \u003clabel\u003e\u003cinput type=\"checkbox\" value=\"Cooking\"\u003e Cooking\u003c/label\u003e\n        \u003cp\u003eSelected Hobbies: \u003cspan id=\"hobbies\"\u003e\u003c/span\u003e\u003c/p\u003e\n\n        \u003c!-- Async Signal --\u003e\n        \u003ch2\u003eFetch Data\u003c/h2\u003e\n        \u003cbutton id=\"fetchData\"\u003eLoad Data\u003c/button\u003e\n        \u003cp id=\"loading\" class=\"loading\" style=\"display: none;\"\u003eLoading...\u003c/p\u003e\n        \u003cp id=\"error\" class=\"error\"\u003e\u003c/p\u003e\n        \u003cp id=\"dataDisplay\"\u003e\u003c/p\u003e\n\n    \u003c/div\u003e\n\n    \u003cscript src=\"https://cdn.jsdelivr.net/gh/nabeelalihashmi/nthjs@main/nthjs.min.js\"\u003e\u003c/script\u003e\n    \u003cscript\u003e\n        // Counter\n        const count = createSignal(0);\n        createBinding(document.getElementById(\"count\"), count);\n        document.getElementById(\"inc\").addEventListener(\"click\", () =\u003e count.value++);\n        document.getElementById(\"dec\").addEventListener(\"click\", () =\u003e count.value--);\n\n        // Name Input\n        const name = createSignal(\"User\");\n        createBinding(document.getElementById(\"nameInput\"), name, \"value\");\n        createBinding(document.getElementById(\"nameDisplay\"), name);\n\n        // Computed Sum\n        const num1 = createSignal(0);\n        const num2 = createSignal(0);\n        const sum = createComputed(() =\u003e parseInt(num1.value) + parseInt(num2.value));\n        createBinding(document.getElementById(\"sum\"), sum);\n\n        document.getElementById(\"num1\").addEventListener(\"input\", e =\u003e num1.value = e.target.value);\n        document.getElementById(\"num2\").addEventListener(\"input\", e =\u003e num2.value = e.target.value);\n\n        // Dynamic List\n        const items = createSignal([]);\n        createListBinding(document.getElementById(\"list\"), items, item =\u003e {\n            const li = document.createElement(\"li\");\n            li.textContent = item.text;\n            return li;\n        });\n\n        document.getElementById(\"addItem\").addEventListener(\"click\", () =\u003e {\n            items.value = [...items.value, { key: Date.now(), text: \"Item \" + (items.value.length + 1) }];\n        });\n\n        // Group Binding (Radio)\n        const selectedColor = createSignal(\"red\");\n        createGroupBinding(document.querySelectorAll(\"input[name=color]\"), selectedColor);\n        createBinding(document.getElementById(\"selectedColor\"), selectedColor);\n\n        // Group Binding (Checkbox)\n        const hobbies = createSignal([]);\n        createGroupBinding(document.querySelectorAll(\"input[type=checkbox]\"), hobbies);\n        createBinding(document.getElementById(\"hobbies\"), hobbies);\n\n        // Async Signal (Fixed)\n        const [dataSignal, fetchData, isLoading, errorSignal] = createAsyncSignal(null, async () =\u003e {\n            const res = await fetch('https://jsonplaceholder.typicode.com/todos/1');\n            if (!res.ok) throw new Error(\"Failed to fetch data!\");\n            return res.json();\n        });\n\n        createEffect(() =\u003e {\n            document.getElementById(\"loading\").style.display = isLoading.value ? \"block\" : \"none\";\n            document.getElementById(\"error\").textContent = errorSignal.value || \"\";\n            document.getElementById(\"dataDisplay\").textContent = dataSignal.value ? \n                `To-Do: ${dataSignal.value.title}` : \"\";\n        });\n\n        document.getElementById(\"fetchData\").addEventListener(\"click\", fetchData);\n    \u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n## API Documentation\n\n### `createSignal(initialValue)`\nCreates a reactive signal.\n\n#### Parameters:\n- `initialValue`: Any value (primitive or object).\n\n#### Returns:\nA signal object with:\n- `.value`: **Getter \u0026 setter** for the reactive state.\n\n#### Usage:\n```js\nconst count = createSignal(0);\nconsole.log(count.value); // 0\n\ncount.value = 5;\nconsole.log(count.value); // 5\n```\n\n---\n\n### `createEffect(effectFn)`\nRuns an effect whenever its dependencies change.\n\n#### Parameters:\n- `effectFn`: A function that depends on reactive signals.\n\n#### Usage:\n```js\nconst count = createSignal(0);\n\ncreateEffect(() =\u003e {\n    console.log(\"Count changed:\", count.value);\n});\n\ncount.value = 10; // Logs \"Count changed: 10\"\n```\n\n---\n\n### `createComputed(computeFn)`\nCreates a computed value that updates when dependencies change.\n\n#### Parameters:\n- `computeFn`: A function returning a derived value.\n\n#### Returns:\nA signal containing the computed value.\n\n#### Usage:\n```js\nconst a = createSignal(2);\nconst b = createSignal(3);\n\nconst sum = createComputed(() =\u003e a.value + b.value);\n\nconsole.log(sum.value); // 5\n\na.value = 10;\nconsole.log(sum.value); // 13\n```\n\n---\n\n### `watch(signal, callback)`\nWatches a signal and executes a callback when its value changes.\n\n#### Parameters:\n- `signal`: The signal to watch.\n- `callback`: Function `(newValue, oldValue) =\u003e {}`.\n\n#### Usage:\n```js\nconst name = createSignal(\"Alice\");\n\nwatch(name, (newVal, oldVal) =\u003e {\n    console.log(`Name changed from ${oldVal} to ${newVal}`);\n});\n\nname.value = \"Bob\"; // Logs: \"Name changed from Alice to Bob\"\n```\n\n---\n\n### `createBinding(element, signal, attribute?)`\nBinds a signal to a DOM element.\n\n#### Parameters:\n- `element`: The target DOM element.\n- `signal`: The reactive signal.\n- `attribute?`: Optional attribute to bind.\n\n#### Usage:\n```js\nconst title = createSignal(\"Hello\");\n\nconst h1 = document.querySelector(\"h1\");\ncreateBinding(h1, title); // Updates text content\n\nconst input = document.querySelector(\"input\");\ncreateBinding(input, title, \"value\"); // Two-way binding\n```\n\n---\n\n### `createListBinding(container, signal, renderItem)`\nBinds a signal array to a container element, rendering child elements.\n\n#### Parameters:\n- `container`: Parent element.\n- `signal`: Reactive array signal.\n- `renderItem`: Function `(item) =\u003e HTMLElement`.\n\n#### Usage:\n```js\nconst items = createSignal([{ key: \"1\", text: \"Item 1\" }]);\n\nconst ul = document.querySelector(\"ul\");\ncreateListBinding(ul, items, (item) =\u003e {\n    const li = document.createElement(\"li\");\n    li.textContent = item.text;\n    return li;\n});\n\nitems.value = [...items.value, { key: \"2\", text: \"Item 2\" }];\n```\n\n---\n\n### `createGroupBinding(elements, signal)`\nBinds a group of radio buttons or checkboxes to a signal.\n\n#### Parameters:\n- `elements`: Array of input elements.\n- `signal`: Reactive signal.\n\n#### Usage:\n**For radio buttons:**\n```js\nconst selected = createSignal(\"option1\");\nconst radios = document.querySelectorAll(\"input[type=radio]\");\n\ncreateGroupBinding(radios, selected);\n```\n\n**For checkboxes:**\n```js\nconst selectedValues = createSignal([\"a\"]);\n\nconst checkboxes = document.querySelectorAll(\"input[type=checkbox]\");\ncreateGroupBinding(checkboxes, selectedValues);\n```\n\n---\n\n## Async Support\n\n### `createAsyncSignal(initialValue, fetcher)`\nCreates a reactive signal that updates from an async function.\n\n#### Parameters:\n- `initialValue`: Default value.\n- `fetcher`: Async function `(signal) =\u003e Promise\u003cdata\u003e`.\n\n#### Usage:\n```js\nconst [data, fetchData, loading, error] = createAsyncSignal(null, async () =\u003e {\n    const response = await fetch(\"https://api.example.com/data\");\n    return response.json();\n});\n\ncreateEffect(() =\u003e {\n    if (loading.value) console.log(\"Loading...\");\n    if (data.value) console.log(\"Data loaded:\", data.value);\n    if (error.value) console.log(\"Error:\", error.value);\n});\n\n// Trigger fetch\nfetchData();\n```\n\n---\n\n## Why Use NthJS?\n- **No build step**: Works directly in the browser.\n- **Tiny \u0026 fast**: Minimal overhead, designed for simplicity.\n- **Fine-grained reactivity**: Updates only the necessary parts of the DOM.\n\n---\n\n## License\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnabeelalihashmi%2Fnthjs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnabeelalihashmi%2Fnthjs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnabeelalihashmi%2Fnthjs/lists"}