{"id":28191624,"url":"https://github.com/modelfoxdotdev/pinwheel","last_synced_at":"2025-12-12T12:32:54.115Z","repository":{"id":50319751,"uuid":"377536548","full_name":"modelfoxdotdev/pinwheel","owner":"modelfoxdotdev","description":null,"archived":false,"fork":false,"pushed_at":"2023-05-12T15:08:04.000Z","size":69,"stargazers_count":22,"open_issues_count":3,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-27T05:32:14.066Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","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/modelfoxdotdev.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}},"created_at":"2021-06-16T15:06:06.000Z","updated_at":"2024-08-17T12:01:17.000Z","dependencies_parsed_at":"2022-08-29T05:30:12.983Z","dependency_job_id":null,"html_url":"https://github.com/modelfoxdotdev/pinwheel","commit_stats":null,"previous_names":["tangramdotdev/pinwheel","tangramxyz/pinwheel"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/modelfoxdotdev/pinwheel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelfoxdotdev%2Fpinwheel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelfoxdotdev%2Fpinwheel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelfoxdotdev%2Fpinwheel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelfoxdotdev%2Fpinwheel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/modelfoxdotdev","download_url":"https://codeload.github.com/modelfoxdotdev/pinwheel/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelfoxdotdev%2Fpinwheel/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262756860,"owners_count":23359597,"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":[],"created_at":"2025-05-16T11:10:24.337Z","updated_at":"2025-12-12T12:32:54.054Z","avatar_url":"https://github.com/modelfoxdotdev.png","language":"Rust","readme":"# Pinwheel\n\nPinwheel is a library for writing web user interfaces with Rust.\n\n## Example\n\nThe example below increments the value in a `\u003cp\u003e` each time a `\u003cbutton\u003e` is pressed.\n\n```rust\nlet body = dom::window().unwrap().document().unwrap().body().unwrap().into();\nlet count = Mutable::new(0);\nlet on_click = {\n  let count = count.clone();\n  move |_| {\n    count.replace_with(|count| *count + 1);\n  }\n};\nlet count_text = count.signal().map(|count| p().child(count.to_string()));\nlet increment_button = button().onclick(on_click).child(\"Increment\");\nlet counter = div()\n  .style(style::DISPLAY, \"grid\")\n  .style(style::JUSTIFY_CONTENT, \"start\")\n  .child_signal(count_text)\n  .child(increment_button);\nApp::new(body, counter).forget();\n```\n\n## Features\n\n### Fine-Grained Reactivity\n\nPinwheel uses the [futures-signals](https://lib.rs/futures-signals) crate to update exactly the right DOM nodes as your application's state changes. No virtual DOM required!\n\n### Isomorphic Rendering\n\nWhen compiled for the browser, Pinwheel renders by creating DOM nodes. On the server, it renders by writing HTML to a string.\n\n```rust\nlet root = p().child(\"Hello, World!\");\n\n// On the server...\nassert_eq!(root.to_string(), \"\u003cp\u003eHello, World!\u003c/p\u003e\");\n\n// On the client...\nApp::new(dom_node, root).forget();\n```\n\n### Partial Hydration\n\nAfter server rendering, make a subset of your app interactive on the client. In the example below, `dynamic_component` will render on the server and the client, but `static_component` will render only on the server.\n\n```rust\nlet root = p().child(\"Hello, World!\");\n\n// On the server...\nlet html = div()\n  .child(static_component)\n  .child(Dehydrate::new(\"hydration_id\", dynamic_component))\n  .to_string();\n\n// On the client...\nhydrate(\"hydration_id\");\n```\n\n### Macro-free Builders\n\nPinwheel provides statically typed builders for DOM elements with no macros, so you get all the benefits of `rustfmt` formatting and `rust-analyzer` autocomplete.\n\n```rust\nlet count_p = count.signal().map(|count| p().child(count.to_string()));\nlet increment_button = button().onclick(on_click).child(\"Increment\");\nlet root = div()\n  .style(style::DISPLAY, \"grid\")\n  .style(style::JUSTIFY_CONTENT, \"start\")\n  .child_signal(count_p)\n  .child(increment_button);\n```\n\n### Components\n\nOrganize your application into self-contained components.\n\n```rust\nuse pinwheel::prelude::*;\n\nstruct Alert {\n  title: String,\n  color: Option\u003cString\u003e,\n  children: Vec\u003cNode\u003e,\n}\n\nimpl Component for Alert {\n  fn into_node(self) -\u003e Node {\n    div()\n      .style(style::BACKGROUND_COLOR, self.color)\n      .child(h1().child(self.title))\n      .children(self.children)\n      .into_node()\n  }\n}\n```\n\n### Component Builders\n\nComponents frequently have a few required fields and many optional fields. Pinwheel provides a derive macro to make using these components easy.\n\n```rust\n#[derive(ComponentBuilder)]\nstruct Alert {\n  // required field\n  title: String,\n  // optional field\n  #[optional]\n  color: Option\u003cString\u003e,\n  #[children]\n  children: Vec\u003cNode\u003e,\n}\n```\n\nNow, you can make an alert with the builder pattern.\n\n```rust\nAlert::new(\"Alert!\")\n  .color(\"green\".to_owned())\n  .child(\"An alert occurred!\")\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodelfoxdotdev%2Fpinwheel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmodelfoxdotdev%2Fpinwheel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodelfoxdotdev%2Fpinwheel/lists"}