{"id":22419204,"url":"https://github.com/iconshot/untrue","last_synced_at":"2026-05-09T10:39:55.018Z","repository":{"id":176996393,"uuid":"655631808","full_name":"iconshot/untrue","owner":"iconshot","description":"JavaScript library for rendering user interfaces.","archived":false,"fork":false,"pushed_at":"2024-02-05T18:33:16.000Z","size":113,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2024-05-05T21:02:50.996Z","etag":null,"topics":["frontend","javascript","ui","web"],"latest_commit_sha":null,"homepage":"https://untrue.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/iconshot.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}},"created_at":"2023-06-19T09:31:25.000Z","updated_at":"2024-05-30T02:40:31.204Z","dependencies_parsed_at":null,"dependency_job_id":"96516a4e-3974-4ffe-971e-3d72856c4c9a","html_url":"https://github.com/iconshot/untrue","commit_stats":null,"previous_names":["iconshot/untrue"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iconshot%2Funtrue","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iconshot%2Funtrue/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iconshot%2Funtrue/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iconshot%2Funtrue/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iconshot","download_url":"https://codeload.github.com/iconshot/untrue/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245784310,"owners_count":20671528,"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":["frontend","javascript","ui","web"],"created_at":"2024-12-05T16:14:32.069Z","updated_at":"2026-05-09T10:39:49.986Z","avatar_url":"https://github.com/iconshot.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [Untrue](https://untrue.dev/)\n\nJavaScript library for rendering user interfaces.\n\n## Installation\n\nThe easiest way to get started with Untrue is through a web app.\n\n```\nnpm i untrue @untrue/web\n```\n\nCompatible with any build tool: [Parcel](https://parceljs.org/), [Vite](https://vitejs.dev/), [Webpack](https://webpack.js.org/), etc.\n\n\u003csub\u003eNative app development available with [Detonator](https://detonator.dev).\u003c/sub\u003e\n\n## Get started\n\nYou can add Untrue to any part of your page.\n\n```ts\nimport $ from \"untrue\";\n\nimport { Tree } from \"@untrue/web\";\n\nimport App from \"./App\";\n\nconst tree = new Tree(document.body);\n\n// $ is a shorthand to represent slots\n\ntree.mount($(App));\n```\n\nIn this case, we're adding Untrue to `body`.\n\nMore on `App` in the next section.\n\n## Basic features\n\n### Interactivity\n\nA component state can change at any time and Untrue knows which nodes should be updated in the DOM.\n\n```ts\nimport $, { Hook } from \"untrue\";\n\nfunction App() {\n  const [counter, updateCounter] = Hook.useState(0);\n\n  const onIncrement = () =\u003e {\n    updateCounter(counter + 1);\n  };\n\n  // regular arrays are used to return a list of slots\n\n  // after the first click, counter is no longer 0 but 1\n\n  return [\n    $(\"span\", counter),\n    $(\"button\", { onclick: onIncrement }, \"increment\"),\n  ];\n}\n\nexport default App;\n```\n\nThe output HTML will be:\n\n```html\n\u003cspan\u003e0\u003c/span\u003e \u003cbutton\u003eincrement\u003c/button\u003e\n```\n\n`button` will have an `onclick` listener attached to it.\n\n`span` will be updated with the new `counter` every time `button` is clicked.\n\n### Modularity\n\nComponents can be classes or functions and are used to group multiple slots.\n\n```ts\nimport $, { Hook, Props } from \"untrue\";\n\nfunction App() {\n  return [\n    $(Header, { title: \"Untrue\" }), // pass title as prop (external data)\n    $(Footer, { year: 2049 }), // pass year as prop (external data)\n  ];\n}\n\ninterface HeaderProps extends Props {\n  title: string;\n}\n\nfunction Header({ title }: HeaderProps) {\n  const [counter, updateCounter] = Hook.useState(0); // internal data\n\n  const onIncrement = () =\u003e {\n    updateCounter(counter + 1);\n  };\n\n  return $(\"header\", [\n    $(\"h1\", title),\n    $(\"div\", [\n      $(\"span\", counter),\n      $(\"button\", { onclick: onIncrement }, \"increment\"),\n    ]),\n  ]);\n}\n\ninterface FooterProps extends Props {\n  year: number;\n}\n\nfunction Footer({ year }: FooterProps) {\n  return $(\"footer\", [\n    $(\"span\", `copyright, ${year}`),\n    $(\"br\"),\n    $(\"a\", { href: \"https://example.com\" }, \"some anchor link\"),\n  ]);\n}\n\nexport default App;\n```\n\nThe output HTML will be:\n\n```html\n\u003cheader\u003e\n  \u003ch1\u003eUntrue\u003c/h1\u003e\n  \u003cdiv\u003e\n    \u003cspan\u003e0\u003c/span\u003e\n    \u003cbutton\u003eincrement\u003c/button\u003e\n  \u003c/div\u003e\n\u003c/header\u003e\n\u003cfooter\u003e\n  \u003cspan\u003ecopyright, 2049\u003c/span\u003e\n  \u003cbr /\u003e\n  \u003ca href=\"https://example.com\"\u003esome anchor link\u003c/a\u003e\n\u003c/footer\u003e\n```\n\n`Header` has some `counter` that will be updated with `button`.\n\n### Lifecycle events\n\n- `mount`: The first render.\n- `update`: Every render after the first one.\n- `render`: Every render. It's fired after `mount` or `update` events.\n- `unmount`: Component has been unmounted.\n\nMultiple event listeners can be attached to a single event. Specially useful to have more organized code.\n\n```ts\nimport $, { Hook } from \"untrue\";\n\nfunction App() {\n  const [running, updateRunning] = Hook.useState(false);\n\n  const onClick = () =\u003e {\n    updateRunning(!running);\n  };\n\n  return [\n    $(\"button\", { onclick: onClick }, running ? \"end timer\" : \"start timer\"),\n    $(\"br\"),\n    running ? $(Timer) : null,\n  ];\n}\n\nfunction Timer() {\n  const [counter, updateCounter] = Hook.useState(0);\n\n  Hook.useMountLifecycle(() =\u003e {\n    console.log(\"Timer mounted\");\n  });\n\n  Hook.useUpdateLifecycle(() =\u003e {\n    console.log(\"Timer updated\");\n  });\n\n  Hook.useEffect(() =\u003e {\n    const timeout = setTimeout(() =\u003e {\n      updateCounter(counter + 1);\n    }, 1000);\n\n    return () =\u003e {\n      clearTimeout(timeout);\n    };\n  }, [counter]);\n\n  return $(\"span\", counter);\n}\n\nexport default App;\n```\n\nAfter the button click, the output HTML will be:\n\n```html\n\u003cbutton\u003eend timer\u003c/button\u003e\n\u003cbr /\u003e\n\u003cspan\u003e0\u003c/span\u003e\n```\n\n`counter` is incremented every second.\n\n`Timer mounted` is logged first followed by `Timer updated` for updates.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficonshot%2Funtrue","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ficonshot%2Funtrue","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficonshot%2Funtrue/lists"}