summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock402
-rw-r--r--flake.lock18
-rw-r--r--rocie-macros/Cargo.toml2
-rw-r--r--rocie-macros/src/form/generate.rs255
-rw-r--r--rocie-macros/src/form/mod.rs11
-rw-r--r--rocie-macros/src/form/parse.rs93
-rw-r--r--src/api/mod.rs56
-rw-r--r--src/components/async_fetch.rs67
-rw-r--r--src/components/banner.rs17
-rw-r--r--src/components/buy.rs212
-rw-r--r--src/components/input_placeholder.rs154
-rw-r--r--src/components/mod.rs1
-rw-r--r--src/components/product_overview.rs8
-rw-r--r--src/components/select_placeholder.rs40
14 files changed, 902 insertions, 434 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 2d545c0..645f23d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,21 +3,6 @@
 version = 4
 
 [[package]]
-name = "addr2line"
-version = "0.25.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
-dependencies = [
- "gimli",
-]
-
-[[package]]
-name = "adler2"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
-
-[[package]]
 name = "aho-corasick"
 version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -42,7 +27,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1384d3fe1eecb464229fcf6eebb72306591c56bf27b373561489458a7c73027d"
 dependencies = [
  "futures",
- "thiserror 2.0.16",
+ "thiserror 2.0.17",
  "wasm-bindgen-futures",
 ]
 
@@ -88,9 +73,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
 
 [[package]]
 name = "attribute-derive"
-version = "0.10.3"
+version = "0.10.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0053e96dd3bec5b4879c23a138d6ef26f2cb936c9cdc96274ac2b9ed44b5bb54"
+checksum = "05832cdddc8f2650cc2cc187cc2e952b8c133a48eb055f35211f61ee81502d77"
 dependencies = [
  "attribute-derive-macro",
  "derive-where",
@@ -102,9 +87,9 @@ dependencies = [
 
 [[package]]
 name = "attribute-derive-macro"
-version = "0.10.3"
+version = "0.10.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "463b53ad0fd5b460af4b1915fe045ff4d946d025fb6c4dc3337752eaa980f71b"
+checksum = "0a7cdbbd4bd005c5d3e2e9c885e6fa575db4f4a3572335b974d8db853b6beb61"
 dependencies = [
  "collection_literals",
  "interpolator",
@@ -123,21 +108,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
 
 [[package]]
-name = "backtrace"
-version = "0.3.76"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
-dependencies = [
- "addr2line",
- "cfg-if",
- "libc",
- "miniz_oxide",
- "object",
- "rustc-demangle",
- "windows-link",
-]
-
-[[package]]
 name = "base16"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -151,9 +121,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
 
 [[package]]
 name = "bitflags"
-version = "2.9.4"
+version = "2.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
+checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
 
 [[package]]
 name = "block-buffer"
@@ -178,15 +148,15 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
 
 [[package]]
 name = "camino"
-version = "1.2.0"
+version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1de8bc0aa9e9385ceb3bf0c152e3a9b9544f6c4a912c8ae504e80c1f0368603"
+checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609"
 
 [[package]]
 name = "cc"
-version = "1.2.39"
+version = "1.2.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f"
+checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
 dependencies = [
  "find-msvc-tools",
  "shlex",
@@ -194,9 +164,9 @@ dependencies = [
 
 [[package]]
 name = "cfg-if"
-version = "1.0.3"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
 
 [[package]]
 name = "chrono"
@@ -212,20 +182,20 @@ dependencies = [
 
 [[package]]
 name = "codee"
-version = "0.3.2"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd8bbfdadf2f8999c6e404697bc08016dce4a3d77dec465b36c9a0652fdb3327"
+checksum = "774365d8238a8dbd57c3047f865187fe6417e765d9955ba8e99e794678a41a0e"
 dependencies = [
  "serde",
  "serde_json",
- "thiserror 2.0.16",
+ "thiserror 2.0.17",
 ]
 
 [[package]]
 name = "collection_literals"
-version = "1.0.2"
+version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26b3f65b8fb8e88ba339f7d23a390fe1b0896217da05e2a66c584c9b29a91df8"
+checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084"
 
 [[package]]
 name = "concurrent-queue"
@@ -238,9 +208,9 @@ dependencies = [
 
 [[package]]
 name = "config"
-version = "0.15.17"
+version = "0.15.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "680d3ac2fe066c43300ec831c978871e50113a708d58ab13d231bd92deca5adb"
+checksum = "180e549344080374f9b32ed41bf3b6b57885ff6a289367b3dbc10eea8acc1918"
 dependencies = [
  "convert_case 0.6.0",
  "pathdiff",
@@ -277,9 +247,9 @@ checksum = "451d0640545a0553814b4c646eb549343561618838e9b42495f466131fe3ad49"
 
 [[package]]
 name = "const_format"
-version = "0.2.34"
+version = "0.2.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd"
+checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad"
 dependencies = [
  "const_format_proc_macros",
 ]
@@ -504,9 +474,9 @@ dependencies = [
 
 [[package]]
 name = "find-msvc-tools"
-version = "0.1.2"
+version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959"
+checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
 
 [[package]]
 name = "fnv"
@@ -615,9 +585,9 @@ dependencies = [
 
 [[package]]
 name = "generic-array"
-version = "0.14.7"
+version = "0.14.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
 dependencies = [
  "typenum",
  "version_check",
@@ -625,25 +595,19 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
 dependencies = [
  "cfg-if",
  "js-sys",
  "libc",
  "r-efi",
- "wasi 0.14.7+wasi-0.2.4",
+ "wasip2",
  "wasm-bindgen",
 ]
 
 [[package]]
-name = "gimli"
-version = "0.32.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
-
-[[package]]
 name = "gloo-net"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -986,9 +950,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.11.4"
+version = "2.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
+checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
 dependencies = [
  "equivalent",
  "hashbrown 0.16.0",
@@ -1003,17 +967,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8"
 
 [[package]]
-name = "io-uring"
-version = "0.7.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
-dependencies = [
- "bitflags",
- "cfg-if",
- "libc",
-]
-
-[[package]]
 name = "ipnet"
 version = "2.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1056,9 +1009,9 @@ dependencies = [
 
 [[package]]
 name = "leptos"
-version = "0.8.9"
+version = "0.8.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52efe8eff3278b12f7897a15bdf067bbbb02212773e379d6fc121592752eb718"
+checksum = "de48e23f7d411ab2f048e2b8f34839c81f6db88489377b045c1099e7e828149e"
 dependencies = [
  "any_spawner",
  "cfg-if",
@@ -1084,7 +1037,7 @@ dependencies = [
  "server_fn",
  "slotmap",
  "tachys",
- "thiserror 2.0.16",
+ "thiserror 2.0.17",
  "throw_error",
  "typed-builder",
  "typed-builder-macro",
@@ -1103,15 +1056,15 @@ dependencies = [
  "config",
  "regex",
  "serde",
- "thiserror 2.0.16",
+ "thiserror 2.0.17",
  "typed-builder",
 ]
 
 [[package]]
 name = "leptos_dom"
-version = "0.8.6"
+version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e920c8b2fd202b25786b0c72a00c745a6962fa923e600df6f3ec352d844be91"
+checksum = "78f4330c88694c5575e0bfe4eecf81b045d14e76a4f8b00d5fd2a63f8779f895"
 dependencies = [
  "js-sys",
  "or_poisoned",
@@ -1130,7 +1083,7 @@ checksum = "0d61ec3e1ff8aaee8c5151688550c0363f85bc37845450764c31ff7584a33f38"
 dependencies = [
  "anyhow",
  "camino",
- "indexmap 2.11.4",
+ "indexmap 2.12.0",
  "parking_lot",
  "proc-macro2",
  "quote",
@@ -1152,9 +1105,9 @@ dependencies = [
 
 [[package]]
 name = "leptos_macro"
-version = "0.8.8"
+version = "0.8.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a84c7e53895c786f1128e91c36a708435e301f487338d19f2f6b5b67bb39ece2"
+checksum = "bb2ef164285bdd94acb91a57a47f26c4ee3f37fc351e67a67642efb74bc3e198"
 dependencies = [
  "attribute-derive",
  "cfg-if",
@@ -1180,7 +1133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2d489e38d3f541e9e43ecc2e3a815527840345a2afca629b3e23fcc1dd254578"
 dependencies = [
  "futures",
- "indexmap 2.11.4",
+ "indexmap 2.12.0",
  "leptos",
  "or_poisoned",
  "send_wrapper",
@@ -1190,9 +1143,9 @@ dependencies = [
 
 [[package]]
 name = "leptos_router"
-version = "0.8.7"
+version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a35447cd0ffcd6fcee0c4e424186c3e967507f36d9d4aaff4ab9976e5faab68"
+checksum = "cfd675546710e67855983437202e09b137ea60588dbb86e7fc0671f618577b73"
 dependencies = [
  "any_spawner",
  "either_of",
@@ -1206,7 +1159,7 @@ dependencies = [
  "rustc_version",
  "send_wrapper",
  "tachys",
- "thiserror 2.0.16",
+ "thiserror 2.0.17",
  "url",
  "wasm-bindgen",
  "web-sys",
@@ -1246,9 +1199,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.176"
+version = "0.2.177"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
+checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
 
 [[package]]
 name = "linear-map"
@@ -1264,11 +1217,10 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
 
 [[package]]
 name = "lock_api"
-version = "0.4.13"
+version = "0.4.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
 dependencies = [
- "autocfg",
  "scopeguard",
 ]
 
@@ -1334,23 +1286,14 @@ dependencies = [
 ]
 
 [[package]]
-name = "miniz_oxide"
-version = "0.8.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
-dependencies = [
- "adler2",
-]
-
-[[package]]
 name = "mio"
-version = "1.0.4"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
+checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
 dependencies = [
  "libc",
- "wasi 0.11.1+wasi-snapshot-preview1",
- "windows-sys 0.59.0",
+ "wasi",
+ "windows-sys 0.61.2",
 ]
 
 [[package]]
@@ -1385,22 +1328,13 @@ dependencies = [
 ]
 
 [[package]]
-name = "object"
-version = "0.37.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
-dependencies = [
- "memchr",
-]
-
-[[package]]
 name = "oco_ref"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed0423ff9973dea4d6bd075934fdda86ebb8c05bdf9d6b0507067d4a1226371d"
 dependencies = [
  "serde",
- "thiserror 2.0.16",
+ "thiserror 2.0.17",
 ]
 
 [[package]]
@@ -1423,9 +1357,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
 
 [[package]]
 name = "parking_lot"
-version = "0.12.4"
+version = "0.12.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
 dependencies = [
  "lock_api",
  "parking_lot_core",
@@ -1433,15 +1367,15 @@ dependencies = [
 
 [[package]]
 name = "parking_lot_core"
-version = "0.9.11"
+version = "0.9.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
 dependencies = [
  "cfg-if",
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-targets",
+ "windows-link",
 ]
 
 [[package]]
@@ -1554,9 +1488,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.101"
+version = "1.0.102"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
+checksum = "8e0f6df8eaa422d97d72edcd152e1451618fed47fabbdbd5a8864167b1d4aff7"
 dependencies = [
  "unicode-ident",
 ]
@@ -1613,16 +1547,16 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
 
 [[package]]
 name = "reactive_graph"
-version = "0.2.7"
+version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37b9e227617c8e257900ea3c9aa536319b138bf961e950a258214edea3c2d591"
+checksum = "2e907f9b4c1ba69f3cd47c85f0d8cacafe681ff2b7cda91ff7d3c1a26432d249"
 dependencies = [
  "any_spawner",
  "async-lock",
  "futures",
  "guardian",
  "hydration_context",
- "indexmap 2.11.4",
+ "indexmap 2.12.0",
  "or_poisoned",
  "pin-project-lite",
  "rustc-hash",
@@ -1630,7 +1564,7 @@ dependencies = [
  "send_wrapper",
  "serde",
  "slotmap",
- "thiserror 2.0.16",
+ "thiserror 2.0.17",
  "web-sys",
 ]
 
@@ -1666,27 +1600,27 @@ dependencies = [
 
 [[package]]
 name = "redox_syscall"
-version = "0.5.17"
+version = "0.5.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
 dependencies = [
  "bitflags",
 ]
 
 [[package]]
 name = "ref-cast"
-version = "1.0.24"
+version = "1.0.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf"
+checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
 dependencies = [
  "ref-cast-impl",
 ]
 
 [[package]]
 name = "ref-cast-impl"
-version = "1.0.24"
+version = "1.0.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7"
+checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1695,9 +1629,9 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.11.3"
+version = "1.12.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
+checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -1707,9 +1641,9 @@ dependencies = [
 
 [[package]]
 name = "regex-automata"
-version = "0.4.11"
+version = "0.4.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
+checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -1718,15 +1652,15 @@ dependencies = [
 
 [[package]]
 name = "regex-syntax"
-version = "0.8.6"
+version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
+checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
 
 [[package]]
 name = "reqwest"
-version = "0.12.23"
+version = "0.12.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb"
+checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
 dependencies = [
  "base64",
  "bytes",
@@ -1813,16 +1747,10 @@ dependencies = [
  "quote",
  "syn",
  "syn_derive",
- "thiserror 2.0.16",
+ "thiserror 2.0.17",
 ]
 
 [[package]]
-name = "rustc-demangle"
-version = "0.1.26"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
-
-[[package]]
 name = "rustc-hash"
 version = "2.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1905,9 +1833,9 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.227"
+version = "1.0.228"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80ece43fc6fbed4eb5392ab50c07334d3e577cbf40997ee896fe7af40bba4245"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
 dependencies = [
  "serde_core",
  "serde_derive",
@@ -1915,18 +1843,18 @@ dependencies = [
 
 [[package]]
 name = "serde_core"
-version = "1.0.227"
+version = "1.0.228"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a576275b607a2c86ea29e410193df32bc680303c82f31e275bbfcafe8b33be5"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.227"
+version = "1.0.228"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51e694923b8824cf0e9b382adf0f60d4e05f348f357b38833a3fa5ed7c2ede04"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1954,7 +1882,7 @@ checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352"
 dependencies = [
  "percent-encoding",
  "serde",
- "thiserror 2.0.16",
+ "thiserror 2.0.17",
 ]
 
 [[package]]
@@ -1970,9 +1898,9 @@ dependencies = [
 
 [[package]]
 name = "serde_spanned"
-version = "1.0.2"
+version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee"
+checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
 dependencies = [
  "serde_core",
 ]
@@ -1991,19 +1919,18 @@ dependencies = [
 
 [[package]]
 name = "serde_with"
-version = "3.14.1"
+version = "3.15.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e"
+checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04"
 dependencies = [
  "base64",
  "chrono",
  "hex",
  "indexmap 1.9.3",
- "indexmap 2.11.4",
+ "indexmap 2.12.0",
  "schemars 0.9.0",
  "schemars 1.0.4",
- "serde",
- "serde_derive",
+ "serde_core",
  "serde_json",
  "serde_with_macros",
  "time",
@@ -2011,9 +1938,9 @@ dependencies = [
 
 [[package]]
 name = "serde_with_macros"
-version = "3.14.1"
+version = "3.15.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e"
+checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955"
 dependencies = [
  "darling",
  "proc-macro2",
@@ -2044,7 +1971,7 @@ dependencies = [
  "serde_json",
  "serde_qs",
  "server_fn_macro_default",
- "thiserror 2.0.16",
+ "thiserror 2.0.17",
  "throw_error",
  "url",
  "wasm-bindgen",
@@ -2119,19 +2046,19 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
 
 [[package]]
 name = "socket2"
-version = "0.6.0"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
+checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
 dependencies = [
  "libc",
- "windows-sys 0.59.0",
+ "windows-sys 0.60.2",
 ]
 
 [[package]]
 name = "stable_deref_trait"
-version = "1.2.0"
+version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
 
 [[package]]
 name = "strsim"
@@ -2141,9 +2068,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
 
 [[package]]
 name = "syn"
-version = "2.0.106"
+version = "2.0.108"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
+checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2184,9 +2111,9 @@ dependencies = [
 
 [[package]]
 name = "tachys"
-version = "0.2.8"
+version = "0.2.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5db6367a7dfbdb427d421ada82425d804bee78ed5297a7c467c10cc993037923"
+checksum = "8b7c22c0a3e8ab7afc9ac34448031316f2e9416f4d489431c5d997a47075fa95"
 dependencies = [
  "any_spawner",
  "async-trait",
@@ -2196,7 +2123,7 @@ dependencies = [
  "erased",
  "futures",
  "html-escape",
- "indexmap 2.11.4",
+ "indexmap 2.12.0",
  "itertools",
  "js-sys",
  "linear-map",
@@ -2227,11 +2154,11 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "2.0.16"
+version = "2.0.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
+checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
 dependencies = [
- "thiserror-impl 2.0.16",
+ "thiserror-impl 2.0.17",
 ]
 
 [[package]]
@@ -2247,9 +2174,9 @@ dependencies = [
 
 [[package]]
 name = "thiserror-impl"
-version = "2.0.16"
+version = "2.0.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
+checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2308,25 +2235,22 @@ dependencies = [
 
 [[package]]
 name = "tokio"
-version = "1.47.1"
+version = "1.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
+checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
 dependencies = [
- "backtrace",
- "io-uring",
  "libc",
  "mio",
  "pin-project-lite",
- "slab",
  "socket2",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
 ]
 
 [[package]]
 name = "toml"
-version = "0.9.7"
+version = "0.9.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0"
+checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
 dependencies = [
  "serde_core",
  "serde_spanned",
@@ -2337,18 +2261,18 @@ dependencies = [
 
 [[package]]
 name = "toml_datetime"
-version = "0.7.2"
+version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1"
+checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
 dependencies = [
  "serde_core",
 ]
 
 [[package]]
 name = "toml_parser"
-version = "1.0.3"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627"
+checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
 dependencies = [
  "winnow",
 ]
@@ -2445,9 +2369,9 @@ dependencies = [
 
 [[package]]
 name = "typenum"
-version = "1.18.0"
+version = "1.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
 
 [[package]]
 name = "unicase"
@@ -2457,9 +2381,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.19"
+version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
+checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
 
 [[package]]
 name = "unicode-segmentation"
@@ -2541,15 +2465,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
 
 [[package]]
-name = "wasi"
-version = "0.14.7+wasi-0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
-dependencies = [
- "wasip2",
-]
-
-[[package]]
 name = "wasip2"
 version = "1.0.1+wasi-0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2708,14 +2623,14 @@ version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
 dependencies = [
- "windows-sys 0.61.1",
+ "windows-sys 0.61.2",
 ]
 
 [[package]]
 name = "windows-core"
-version = "0.62.1"
+version = "0.62.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
 dependencies = [
  "windows-implement",
  "windows-interface",
@@ -2726,9 +2641,9 @@ dependencies = [
 
 [[package]]
 name = "windows-implement"
-version = "0.60.1"
+version = "0.60.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2737,9 +2652,9 @@ dependencies = [
 
 [[package]]
 name = "windows-interface"
-version = "0.59.2"
+version = "0.59.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2748,52 +2663,53 @@ dependencies = [
 
 [[package]]
 name = "windows-link"
-version = "0.2.0"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
 
 [[package]]
 name = "windows-result"
-version = "0.4.0"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
 dependencies = [
  "windows-link",
 ]
 
 [[package]]
 name = "windows-strings"
-version = "0.5.0"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
 dependencies = [
  "windows-link",
 ]
 
 [[package]]
 name = "windows-sys"
-version = "0.59.0"
+version = "0.60.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
 dependencies = [
  "windows-targets",
 ]
 
 [[package]]
 name = "windows-sys"
-version = "0.61.1"
+version = "0.61.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
 dependencies = [
  "windows-link",
 ]
 
 [[package]]
 name = "windows-targets"
-version = "0.52.6"
+version = "0.53.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
 dependencies = [
+ "windows-link",
  "windows_aarch64_gnullvm",
  "windows_aarch64_msvc",
  "windows_i686_gnu",
@@ -2806,51 +2722,51 @@ dependencies = [
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.52.6"
+version = "0.53.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.52.6"
+version = "0.53.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.52.6"
+version = "0.53.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
 
 [[package]]
 name = "windows_i686_gnullvm"
-version = "0.52.6"
+version = "0.53.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.52.6"
+version = "0.53.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.52.6"
+version = "0.53.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.52.6"
+version = "0.53.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.52.6"
+version = "0.53.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
 
 [[package]]
 name = "winnow"
diff --git a/flake.lock b/flake.lock
index fb9e6c6..6f9f237 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
   "nodes": {
     "nixpkgs": {
       "locked": {
-        "lastModified": 1758736440,
-        "narHash": "sha256-ssTkeaADdxhQl8y1ByejG5TlYakYElAIRhxnfYoQTRk=",
+        "lastModified": 1761164809,
+        "narHash": "sha256-3uM91Lx9WZomE6MMEBorJyEyBNiHWRIxza/GganDxew=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "2cd3cac16691a933e94276f0a810453f17775c28",
+        "rev": "3d2db9755e7815937fb7b8f089fad9b44bc416d8",
         "type": "github"
       },
       "original": {
@@ -30,11 +30,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1758767687,
-        "narHash": "sha256-znUulOqcL/Kkdr7CkyIi8Z1pTGXpi54Xg2FmlyJmv4A=",
+        "lastModified": 1761100675,
+        "narHash": "sha256-LX3TCDBeNpCWTDXtGyRASVcLmRPChSli34bgHnZ1DCw=",
         "owner": "oxalica",
         "repo": "rust-overlay",
-        "rev": "b8bcc09d4f627f4e325408f6e7a85c3ac31f0eeb",
+        "rev": "72161c6c53f6e3f8dadaf54b2204a5094c6a16ae",
         "type": "github"
       },
       "original": {
@@ -50,11 +50,11 @@
         ]
       },
       "locked": {
-        "lastModified": 1758728421,
-        "narHash": "sha256-ySNJ008muQAds2JemiyrWYbwbG+V7S5wg3ZVKGHSFu8=",
+        "lastModified": 1760945191,
+        "narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=",
         "owner": "numtide",
         "repo": "treefmt-nix",
-        "rev": "5eda4ee8121f97b218f7cc73f5172098d458f1d1",
+        "rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2",
         "type": "github"
       },
       "original": {
diff --git a/rocie-macros/Cargo.toml b/rocie-macros/Cargo.toml
index 01e9e5c..afbb942 100644
--- a/rocie-macros/Cargo.toml
+++ b/rocie-macros/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2024"
 [dependencies]
 proc-macro2 = "1.0.101"
 quote = "1.0.41"
-syn = {version = "2.0.106", features = []}
+syn = { version = "2.0.106", features = ["full"] }
 prettyplease = "0.2"
 
 [lib]
diff --git a/rocie-macros/src/form/generate.rs b/rocie-macros/src/form/generate.rs
index 6673ba5..5642e6a 100644
--- a/rocie-macros/src/form/generate.rs
+++ b/rocie-macros/src/form/generate.rs
@@ -1,7 +1,7 @@
 use proc_macro::TokenStream;
 use proc_macro2::TokenStream as TokenStream2;
 use quote::{format_ident, quote};
-use syn::{Ident, parse_macro_input};
+use syn::{Ident, Type, parse_macro_input};
 
 use crate::form::{ParsedChild, ParsedInput};
 
@@ -10,6 +10,8 @@ pub fn form_impl(item: TokenStream) -> TokenStream {
 
     let node_refs = input.children.iter().map(node_ref);
     let signals = input.children.iter().map(signal);
+    let reactive_signals = input.children.iter().map(reactive_signal);
+    let auto_complete_signals = input.children.iter().map(auto_complete_signal);
 
     let type_verifications = input.children.iter().map(type_verification);
 
@@ -20,6 +22,11 @@ pub fn form_impl(item: TokenStream) -> TokenStream {
 
     let bounds = input.on_submit.inputs;
     let on_submit_block = input.on_submit.block;
+    let on_submit_move = if input.on_submit.should_use_move {
+        quote! {move}
+    } else {
+        quote! {}
+    };
 
     let input_htmls = input.children.iter().map(input_html);
 
@@ -50,11 +57,11 @@ pub fn form_impl(item: TokenStream) -> TokenStream {
 
         #(
             #node_refs
-        )*
-        #(
+
             #signals
-        )*
-        #(
+            #reactive_signals
+            #auto_complete_signals
+
             #type_verifications
         )*
 
@@ -68,7 +75,7 @@ pub fn form_impl(item: TokenStream) -> TokenStream {
                 #fetch_values
             )*
 
-            let real_on_submit = |Inputs {#(#bounds),*}| #on_submit_block;
+            let real_on_submit = #on_submit_move |Inputs {#(#bounds),*}| #on_submit_block;
             real_on_submit(
                     #output_struct_construction
             )
@@ -76,16 +83,17 @@ pub fn form_impl(item: TokenStream) -> TokenStream {
 
 
         view! {
-            <form class="flex flex-col contents-start m-2 g-2" on:submit=on_submit>
+            <form class="relative flex flex-col contents-start m-2 g-2" on:submit=on_submit>
                 #(
                     #input_htmls
                 )*
 
-                <div class="static">
+                <div class="sticky bottom-2 left-0 flex flex-row contents-start">
+                    <div class="w-5/6" />
                     <input
                         type="submit"
                         value="Submit"
-                        class="absolute bottom-0 right-0 h-20 w-20 rounded-lg bg-green-300 m-2"
+                        class="pr-2 pl-2 rounded-lg bg-green-300 m-2"
                     />
                 </div>
             </form>
@@ -112,15 +120,20 @@ fn node_ref_name(name: &Ident) -> Ident {
 }
 
 fn node_ref(child: &ParsedChild) -> TokenStream2 {
-    let (name, ty) = match child {
-        ParsedChild::Input { name, .. } => (name, quote! {Input}),
-        ParsedChild::Select { name, .. } => (name, quote! {Select}),
-    };
+    if let ParsedChild::Show { children, .. } = child {
+        children.iter().map(node_ref).collect()
+    } else {
+        let (name, ty) = match child {
+            ParsedChild::Input { name, .. } => (name, quote! {Input}),
+            ParsedChild::Select { name, .. } => (name, quote! {Select}),
+            ParsedChild::Show { .. } => unreachable!("Filtered out before"),
+        };
 
-    let node_ref_name = node_ref_name(name);
+        let node_ref_name = node_ref_name(name);
 
-    quote! {
-        let #node_ref_name: NodeRef<#ty> = NodeRef::new();
+        quote! {
+            let #node_ref_name: NodeRef<#ty> = NodeRef::new();
+        }
     }
 }
 
@@ -137,42 +150,152 @@ fn signal(child: &ParsedChild) -> TokenStream2 {
             }
         }
         ParsedChild::Select { .. } => quote! {},
+        ParsedChild::Show { children, .. } => children.iter().map(signal).collect(),
     }
 }
-fn type_verification(child: &ParsedChild) -> TokenStream2 {
+
+fn signal_auto_complete_names(name: &Ident) -> (Ident, Ident) {
+    (
+        format_ident!("{name}_auto_complete_get"),
+        format_ident!("{name}_auto_complete_set"),
+    )
+}
+fn auto_complete_signal(child: &ParsedChild) -> TokenStream2 {
     match child {
-        ParsedChild::Input { .. } => quote! {},
-        ParsedChild::Select {
-            rust_type, options, ..
+        ParsedChild::Input {
+            name,
+            reactive,
+            auto_complete,
+            ..
         } => {
-            let names: Vec<_> = options
-                .0
-                .iter()
-                .enumerate()
-                .map(|(index, _)| format_ident!("name_{index}"))
-                .collect();
-            let values: Vec<_> = options.0.iter().map(|option| option.1.clone()).collect();
+            if let Some(auto_complete) = auto_complete {
+                let (signal_auto_complete_get, _) = signal_auto_complete_names(name);
+
+                let reactive = reactive
+                    .as_ref()
+                    .expect("Must be some, if auto_complete is some");
+
+                quote! {
+                    let #signal_auto_complete_get =
+                        leptos::prelude::LocalResource::new(
+                            move || #auto_complete(#reactive())
+                        );
+                }
+            } else {
+                quote! {}
+            }
+        }
+        ParsedChild::Select { .. } => quote! {},
+        ParsedChild::Show { children, .. } => children.iter().map(auto_complete_signal).collect(),
+    }
+}
 
+fn signal_reactive_base_names(name: &Ident) -> (Ident, Ident) {
+    (
+        format_ident!("{name}_reactive_base_get"),
+        format_ident!("{name}_reactive_base_set"),
+    )
+}
+fn reactive_signal(child: &ParsedChild) -> TokenStream2 {
+    match child {
+        ParsedChild::Input {
+            name,
+            reactive,
+            rust_type,
+            ..
+        } => {
+            if let Some(reactive) = reactive {
+                let (signal_reactive_base_get, signal_reactive_base_set) =
+                    signal_reactive_base_names(name);
+
+                quote! {
+                    let (#signal_reactive_base_get, #signal_reactive_base_set) = signal(None);
+                    #reactive = move || {
+                         {
+                            let output: Option<String> = #signal_reactive_base_get.get();
+
+                            if let Some(output) = output {
+                                let fin: Result<#rust_type, leptos::error::Error> = output
+                                            .parse()
+                                            .map_err(Into::<leptos::error::Error>::into);
+
+                                match fin {
+                                    Ok(ok) => {
+                                        Some(ok)
+                                    },
+                                    Err(err) => {
+                                        // Reset the signal
+                                        None
+                                    }
+                                }
+                            } else {
+                                None
+                            }
+                        }
+                    };
+                }
+            } else {
+                quote! {}
+            }
+        }
+        ParsedChild::Select { .. } => quote! {},
+        ParsedChild::Show { children, .. } => children.iter().map(reactive_signal).collect(),
+    }
+}
+
+fn type_verification(child: &ParsedChild) -> TokenStream2 {
+    match child {
+        ParsedChild::Input { .. } => {
+            quote! {}
+        }
+        ParsedChild::Select { name, options, .. } => {
+            let name = format_ident!("_select_val_{name}");
             quote! {
                 {
-                    #(
-                        let #names: #rust_type = #values;
-                    )*
+                    let #name:
+                            leptos::prelude::LocalResource<
+                                Result<
+                                    // TODO: Use #rust_type instead of the second String <2025-10-21>
+                                    Vec<(String, String)>,
+                                    leptos::error::Error
+                                >
+                            > = #options;
                 }
             }
         }
+        ParsedChild::Show { when: _, children } => {
+            let base = children
+                .iter()
+                .map(type_verification)
+                .collect::<TokenStream2>();
+
+            quote! {
+                #base
+            }
+        }
+    }
+}
+
+fn get_names(input: &ParsedChild) -> Vec<&Ident> {
+    match input {
+        ParsedChild::Input { name, .. } => vec![name],
+        ParsedChild::Select { name, .. } => vec![name],
+        ParsedChild::Show { children, .. } => children.iter().flat_map(get_names).collect(),
     }
 }
 
 fn output_struct_definition(children: &[ParsedChild]) -> TokenStream2 {
-    let names = children.iter().map(|child| match child {
-        ParsedChild::Input { name, .. } => name,
-        ParsedChild::Select { name, .. } => name,
-    });
-    let rust_types = children.iter().map(|child| match child {
-        ParsedChild::Input { rust_type, .. } => rust_type,
-        ParsedChild::Select { rust_type, .. } => rust_type,
-    });
+    fn get_rust_types(input: &ParsedChild) -> Vec<&Type> {
+        match input {
+            ParsedChild::Input { rust_type, .. } => vec![rust_type],
+            ParsedChild::Select { rust_type, .. } => vec![rust_type],
+            ParsedChild::Show { children, .. } => {
+                children.iter().flat_map(get_rust_types).collect()
+            }
+        }
+    }
+    let names = children.iter().flat_map(get_names);
+    let rust_types = children.iter().flat_map(get_rust_types);
 
     quote! {
         struct Inputs {
@@ -183,10 +306,7 @@ fn output_struct_definition(children: &[ParsedChild]) -> TokenStream2 {
     }
 }
 fn output_struct_construction(children: &[ParsedChild]) -> TokenStream2 {
-    let names = children.iter().map(|child| match child {
-        ParsedChild::Input { name, .. } => name,
-        ParsedChild::Select { name, .. } => name,
-    });
+    let names = children.iter().flat_map(get_names);
 
     quote! {
         Inputs {
@@ -270,6 +390,9 @@ fn fetch_value(child: &ParsedChild) -> TokenStream2 {
                 };
             }
         }
+        ParsedChild::Show { children, .. } => {
+            children.iter().map(fetch_value).collect::<TokenStream2>()
+        }
     }
 }
 
@@ -280,13 +403,39 @@ fn input_html(child: &ParsedChild) -> TokenStream2 {
             label,
             rust_type,
             html_type,
+            reactive,
+            auto_complete,
             ..
         } => {
             let node_ref_name = node_ref_name(name);
             let (signal_name_get, _) = signal_names(name);
+            let reactive = {
+                if reactive.is_some() {
+                    let (_, signal_reactive_set) = signal_reactive_base_names(name);
+
+                    quote! {
+                        reactive=Some(#signal_reactive_set)
+                    }
+                } else {
+                    quote! {}
+                }
+            };
+
+            let auto_complete = {
+                if auto_complete.is_some() {
+                    let (signal_auto_complete_get, _) = signal_auto_complete_names(name);
+
+                    quote! {
+                        auto_complete=Some(#signal_auto_complete_get)
+                    }
+                } else {
+                    quote! {}
+                }
+            };
 
             quote! {
-                <InputPlaceholder input_type=#html_type label=#label node_ref=#node_ref_name />
+                <InputPlaceholder #reactive #auto_complete input_type=#html_type label=#label node_ref=#node_ref_name />
+
                 <Show
                    when=move || #signal_name_get.get().is_some()
                    fallback=|| ()
@@ -308,14 +457,22 @@ fn input_html(child: &ParsedChild) -> TokenStream2 {
             ..
         } => {
             let node_ref_name = node_ref_name(name);
-            let options = options.0.iter().map(|(lit, expr)| {
-                quote! {
-                    (#lit, #expr.to_string())
-                }
-            });
 
             quote! {
-                <SelectPlaceholder label=#label node_ref=#node_ref_name options=vec![#(#options),*] />
+                <SelectPlaceholder label=#label node_ref=#node_ref_name options=#options />
+            }
+        }
+        ParsedChild::Show { when, children } => {
+            let rendered_children = children.iter().map(input_html).collect::<Vec<_>>();
+
+            quote! {
+                <Show
+                    when=#when
+                >
+                    #(
+                        #rendered_children
+                     )*
+                </Show>
             }
         }
     }
diff --git a/rocie-macros/src/form/mod.rs b/rocie-macros/src/form/mod.rs
index 2719826..7abd8f6 100644
--- a/rocie-macros/src/form/mod.rs
+++ b/rocie-macros/src/form/mod.rs
@@ -1,7 +1,7 @@
 use syn::{Expr, Ident, LitStr, Type};
 
-mod parse;
 mod generate;
+mod parse;
 
 pub use generate::form_impl;
 
@@ -9,6 +9,7 @@ pub use generate::form_impl;
 pub struct ParsedOnSubmit {
     inputs: Vec<Ident>,
     block: Expr,
+    pub(crate) should_use_move: bool,
 }
 
 #[derive(Debug)]
@@ -18,19 +19,19 @@ pub enum ParsedChild {
         rust_type: Type,
         html_type: LitStr,
         label: LitStr,
+        reactive: Option<Ident>,
+        auto_complete: Option<Ident>,
     },
     Select {
         name: Ident,
         label: LitStr,
         rust_type: Type,
-        options: SelectOptions,
+        options: Expr, // Vec<(String, Uuid)>
     },
+    Show { when: Expr, children: Vec<ParsedChild> },
 }
 
 #[derive(Debug)]
-pub struct SelectOptions(Vec<(LitStr, Expr)>);
-
-#[derive(Debug)]
 pub struct ParsedInput {
     on_submit: ParsedOnSubmit,
     children: Vec<ParsedChild>,
diff --git a/rocie-macros/src/form/parse.rs b/rocie-macros/src/form/parse.rs
index ef2087b..b0ca58c 100644
--- a/rocie-macros/src/form/parse.rs
+++ b/rocie-macros/src/form/parse.rs
@@ -1,7 +1,10 @@
 use quote::format_ident;
-use syn::{bracketed, parenthesized, parse::{Parse, ParseStream}, Expr, Ident, LitStr, Token, Type};
+use syn::{
+    Expr, Ident, LitStr, Token, Type,
+    parse::{Parse, ParseStream},
+};
 
-use crate::form::{ParsedChild, ParsedInput, ParsedOnSubmit, SelectOptions};
+use crate::form::{ParsedChild, ParsedInput, ParsedOnSubmit};
 
 macro_rules! parse_key_value {
     ($input:expr, $name:ident as $ty:ty) => {{
@@ -19,27 +22,13 @@ macro_rules! parse_key_value {
 
         value
     }};
-}
-
-impl Parse for SelectOptions {
-    fn parse(input: ParseStream) -> syn::Result<Self> {
-        let content;
-        bracketed!(content in input);
-        let inner = content.parse_terminated(
-            |arg| {
-                let paren;
-                parenthesized!(paren in arg);
-                let lit = paren.parse::<LitStr>()?;
-                paren.parse::<Token![,]>()?;
-                let expr = paren.parse::<Expr>()?;
-
-                Ok((lit, expr))
-            },
-            Token![,],
-        )?;
-
-        Ok(Self(inner.into_iter().collect()))
-    }
+    (@option $input:expr, $name:ident as $ty:ty) => {{
+        if $input.peek(Ident) {
+            Some(parse_key_value!($input, $name as $ty))
+        } else {
+            None
+        }
+    }};
 }
 
 impl Parse for ParsedInput {
@@ -78,19 +67,33 @@ impl Parse for ParsedChild {
                 let rust_type = parse_key_value!(input, rust_type as Type);
                 let html_type = parse_key_value!(input, html_type as LitStr);
                 let label = parse_key_value!(input, label as LitStr);
+                let reactive = parse_key_value!(@option input, reactive as Ident);
+                let auto_complete = parse_key_value!(@option input, auto_complete as Ident);
+
+                if auto_complete.is_some() && reactive.is_none() {
+                    panic!("Cannot provide an autocomplet, without registering an reactive signal")
+                }
+
+                input.parse::<Token![/]>()?;
+                input.parse::<Token![>]>()?;
 
                 ParsedChild::Input {
                     name,
                     rust_type,
                     html_type,
                     label,
+                    reactive,
+                    auto_complete,
                 }
             }
             variant if variant == format_ident!("Select") => {
                 let name = parse_key_value!(input, name as Ident);
                 let rust_type = parse_key_value!(input, rust_type as Type);
                 let label = parse_key_value!(input, label as LitStr);
-                let options = parse_key_value!(input, options as SelectOptions);
+                let options = parse_key_value!(input, options as Expr);
+
+                input.parse::<Token![/]>()?;
+                input.parse::<Token![>]>()?;
 
                 ParsedChild::Select {
                     name,
@@ -99,11 +102,36 @@ impl Parse for ParsedChild {
                     rust_type,
                 }
             }
-            _ => panic!("Unkown form child variant: {variant}"),
-        };
+            variant if variant == format_ident!("Show") => {
+                let when = parse_key_value!(input, when as Expr);
 
-        input.parse::<Token![/]>()?;
-        input.parse::<Token![>]>()?;
+                input.parse::<Token![>]>()?;
+
+                let children = {
+                    let mut children = vec![];
+
+                    while !(input.peek(Token![<]) && input.peek2(Token![/])) {
+                        children.push(input.parse::<ParsedChild>()?);
+                    }
+
+                    {
+                        input.parse::<Token![<]>()?;
+                        input.parse::<Token![/]>()?;
+                        let show_ident = input.parse::<Ident>()?;
+
+                        if show_ident != format_ident!("Show") {
+                            panic!("Expected key name to be 'Show', but found: '{show_ident}'",);
+                        }
+                        input.parse::<Token![>]>()?;
+
+                        children
+                    }
+                };
+
+                ParsedChild::Show { when, children }
+            }
+            _ => panic!("Unknown form child variant: {variant}"),
+        };
 
         Ok(output)
     }
@@ -113,6 +141,13 @@ impl Parse for ParsedOnSubmit {
     fn parse(input: ParseStream) -> syn::Result<Self> {
         let mut inputs = Vec::new();
 
+        let should_use_move = if input.peek(Token![move]) {
+            input.parse::<Token![move]>()?;
+            true
+        } else {
+            false
+        };
+
         input.parse::<Token![|]>()?;
         while !input.peek(Token![|]) {
             inputs.push(input.parse::<Ident>()?);
@@ -127,6 +162,6 @@ impl Parse for ParsedOnSubmit {
 
         input.parse::<Token![;]>()?;
 
-        Ok(Self { inputs, block })
+        Ok(Self { inputs, block, should_use_move })
     }
 }
diff --git a/src/api/mod.rs b/src/api/mod.rs
index 8b9e77d..3879223 100644
--- a/src/api/mod.rs
+++ b/src/api/mod.rs
@@ -6,10 +6,16 @@ use reactive_stores::Store;
 use rocie_client::{
     apis::{
         api_get_inventory_api::amount_by_id,
-        api_get_product_api::{product_by_id, products},
+        api_get_product_api::{
+            product_by_id, product_by_name, product_suggestion_by_name, products,
+        },
         api_get_unit_api::unit_by_id,
+        api_get_unit_property_api::unit_property_by_id,
+        api_set_barcode_api::buy_barcode, configuration::Configuration,
+    },
+    models::{
+        BarcodeId, Product, ProductAmount, ProductId, Unit, UnitId, UnitProperty, UnitPropertyId,
     },
-    models::{Product, ProductAmount, ProductId, Unit, UnitId},
 };
 
 use crate::{ConfigState, ConfigStateStoreFields};
@@ -26,12 +32,36 @@ pub(crate) async fn get_product_by_id(product_id: ProductId) -> Result<Product,
         .await
         .map_err(Into::<Error>::into)
 }
+pub(crate) async fn get_product_by_name(
+    name: String,
+) -> Result<
+    Product,
+    rocie_client::apis::Error<rocie_client::apis::api_get_product_api::ProductByNameError>,
+> {
+    let config = expect_context::<Store<ConfigState>>();
+    product_by_name(&config.config().read(), &name).await
+}
+pub(crate) async fn get_products_by_part_name(part_name: String) -> Result<Vec<Product>, Error> {
+    let config = expect_context::<Store<ConfigState>>();
+    product_suggestion_by_name(&config.config().read(), &part_name)
+        .await
+        .map_err(Into::<Error>::into)
+}
 pub(crate) async fn get_unit_by_id(unit_id: UnitId) -> Result<Unit, Error> {
     let config = expect_context::<Store<ConfigState>>();
     unit_by_id(&config.config().read(), unit_id)
         .await
         .map_err(Into::<Error>::into)
 }
+pub(crate) async fn get_unit_property_by_id(
+    unit_id: UnitPropertyId,
+) -> Result<UnitProperty, Error> {
+    let config = expect_context::<Store<ConfigState>>();
+    unit_property_by_id(&config.config().read(), unit_id)
+        .await
+        .map_err(Into::<Error>::into)
+}
+
 pub(crate) async fn get_full_product_by_id(
     id: ProductId,
 ) -> Result<(Product, ProductAmount, Unit), Error> {
@@ -41,6 +71,14 @@ pub(crate) async fn get_full_product_by_id(
 
     Ok::<_, Error>((product, amount, unit))
 }
+pub(crate) async fn get_product_unit_by_id(
+    id: ProductId,
+) -> Result<(Product, UnitProperty), Error> {
+    let product = get_product_by_id(id).await?;
+    let unit = get_unit_property_by_id(product.unit_property).await?;
+
+    Ok::<_, Error>((product, unit))
+}
 
 pub(crate) async fn get_products() -> Result<Vec<Product>, Error> {
     let config = expect_context::<Store<ConfigState>>();
@@ -48,3 +86,17 @@ pub(crate) async fn get_products() -> Result<Vec<Product>, Error> {
         .await
         .map_err(Into::<Error>::into)
 }
+
+pub(crate) async fn buy_barcode_wrapper(
+    config: &Configuration,
+    barcode_number: u32,
+) -> Result<(), Error> {
+    buy_barcode(
+        config,
+        BarcodeId {
+            value: barcode_number,
+        },
+    )
+    .await
+    .map_err(Into::<Error>::into)
+}
diff --git a/src/components/async_fetch.rs b/src/components/async_fetch.rs
index 7105c6f..f24e3a5 100644
--- a/src/components/async_fetch.rs
+++ b/src/components/async_fetch.rs
@@ -1,5 +1,23 @@
+macro_rules! AsyncResource {
+    (
+        (
+            $(
+                $input_name:ident : $input_type:ty = $input:expr
+            ),*
+        ) -> $output:ty $fetcher:block
+    ) => {{
+        async fn fetcher($($input_name : $input_type),*) -> $output $fetcher
+
+        leptos::prelude::LocalResource::new(move || fetcher($($input),*))
+    }}
+}
+pub(crate) use AsyncResource;
+
 macro_rules! AsyncFetch {
-    (fetcher = $fetcher:block producer = |$bound_variable:pat_param| $producer:block) => {{
+    (
+        fetcher = $fetcher:block
+        producer = |$bound_variable:pat_param| $producer:block
+    ) => {{
         use leptos::{
             prelude::{ElementChild, LocalResource, Suspend, Transition},
             view,
@@ -18,33 +36,24 @@ macro_rules! AsyncFetch {
             </Transition>
         }
     }};
+    (
+        @map_error_in_producer
+        from_resource = $resource:ident
+        producer = |$bound_variable:pat_param| $producer:block
+    ) => {{
+        use leptos::prelude::{ElementChild, Suspend, Transition};
+
+        leptos::view! {
+            <Transition fallback=|| {
+                view! { <p>"Loading..."</p> }
+            }>
+                {move || Suspend::new(async move {
+                    $resource
+                        .await
+                        .map(|$bound_variable| $producer)
+                })}
+            </Transition>
+        }
+    }};
 }
 pub(crate) use AsyncFetch;
-
-// #[component]
-// pub fn AsyncFetch<P, V, T, Fut>(
-//     fetcher: impl Fn() -> Fut + 'static + Send + Sync,
-//     producer: P,
-// ) -> impl IntoView
-// where
-//     V: IntoView + 'static,
-//     P: Fn(T) -> V + 'static + Send + Sync,
-//     Fut: Future<Output = Result<T, Error>> + 'static,
-//     T: 'static,
-//     LocalResource<Result<T, Error>>: IntoFuture<Output = Result<T, Error>> + Send,
-// {
-//     view! {
-//             <Transition fallback=|| {
-//                 view! { <p>"Loading..."</p> }
-//             }>
-//                 { || Suspend::new(async {
-//                     let value_resource = LocalResource::new( || fetcher());
-//                     value_resource
-//                         .await
-//                         .map(|value| {
-//                             producer(value)
-//                         })
-//                 })}
-//             </Transition>
-//     }
-// }
diff --git a/src/components/banner.rs b/src/components/banner.rs
new file mode 100644
index 0000000..acaaf62
--- /dev/null
+++ b/src/components/banner.rs
@@ -0,0 +1,17 @@
+use leptos::{
+    IntoView, component,
+    prelude::{ClassAttribute, ElementChild},
+    view,
+};
+
+#[component]
+pub fn Banner<T>(mut text: T) -> impl IntoView
+where
+    T: FnMut() -> String + Send + 'static,
+{
+    view! {
+        <p class="text-white rounded-lg m-2 p-2 bg-red-600">
+            {move || text()}
+        </p>
+    }
+}
diff --git a/src/components/buy.rs b/src/components/buy.rs
index 6d9402e..cb4cff4 100644
--- a/src/components/buy.rs
+++ b/src/components/buy.rs
@@ -1,41 +1,193 @@
-use leptos::{IntoView, component, view};
+use leptos::{
+    IntoView, component,
+    prelude::{Get, Read, Show, WriteSignal, expect_context, signal},
+    task::spawn_local,
+    view,
+};
+use leptos_router::{NavigateOptions, hooks::use_navigate};
 use log::info;
-use rocie_client::models::UnitId;
+use reactive_stores::Store;
+use rocie_client::{
+    apis::Error,
+    models::{Product, Unit},
+};
 use uuid::Uuid;
 
-use crate::components::{form::Form, site_header::SiteHeader};
+use crate::{
+    ConfigState, ConfigStateStoreFields,
+    api::{
+        buy_barcode_wrapper, get_product_by_name, get_product_unit_by_id,
+        get_products_by_part_name, get_unit_by_id,
+    },
+    components::{async_fetch::AsyncResource, banner::Banner, form::Form, site_header::SiteHeader},
+};
 
 #[component]
 pub fn Buy() -> impl IntoView {
+    let (on_submit_errored, on_submit_errored_set) = signal(None);
+
+    view! {
+        <SiteHeader logo=icondata_io::IoPricetag back_location="/" name="Buy" />
+
+        <Show when=move || on_submit_errored.get().is_some()>
+            <Banner text=move || on_submit_errored.get().expect("Should be some") />
+        </Show>
+
+        {
+            Form! {
+                on_submit = |barcode_number, amount| {
+                    let config = expect_context::<Store<ConfigState>>();
+                    let config = config.config().read();
+
+                    spawn_local(async move {
+                        if let Err(err) = buy_barcode_wrapper(&config, barcode_number).await {
+                            let error = format!("Error in form on-submit for barcode `{barcode_number}`: {err}");
+                            on_submit_errored_set.set(Some(error));
+                        } else {
+                            on_submit_errored_set.set(None);
+
+                            info!("Bought barcode {barcode_number} {amount} times");
+                        }
+
+                    });
+                };
+
+                <Input
+                    name=barcode_number,
+                    rust_type=u32,
+                    html_type="number",
+                    label="Barcode Number",
+                />
+
+                <Input
+                    name=amount,
+                    rust_type=u16,
+                    html_type="number",
+                    label="Amount"
+                />
+            }
+        }
+    }
+}
+
+#[component]
+pub fn AssociateBarcode() -> impl IntoView {
+    let product_name_signal;
+
+    let (show_units, show_units_set) = signal(false);
+
     view! {
         <SiteHeader logo=icondata_io::IoPricetag back_location="/" name="Buy" />
 
-        {Form! {
-            on_submit = |product_barcode, amount, unit_id| {
-                info!("Got product barcode: {product_barcode} with amount: {amount}, {unit_id}");
-            };
-
-            <Input
-                name=product_barcode,
-                rust_type=u32,
-                html_type="number",
-                label="Product Barcode"
-            />
-            <Select
-                name=unit_id,
-                rust_type=Uuid,
-                label="Unit",
-                options=[
-                    ("Kilogram", Uuid::new_v4()),
-                    ("Gram",  Uuid::new_v4())
-                ]
-            />
-            <Input
-                name=amount,
-                rust_type=u16,
-                html_type="number",
-                label="Amount"
-            />
-        }}
+        {
+            Form! {
+                on_submit = |product_name, amount, unit_id| {
+                    spawn_local(async move {
+                        let navigate = use_navigate();
+
+                        info!("Got product barcode: {product_name} with amount: {amount}, and {unit_id}");
+
+                        navigate("/", NavigateOptions::default());
+                    });
+                };
+
+                <Input
+                    name=product_name,
+                    rust_type=String,
+                    html_type="text",
+                    label="Product Name",
+                    reactive=product_name_signal
+                    auto_complete=generate_suggest_products
+                />
+
+                <Show
+                    when=move || show_units.get(),
+                >
+                    <Select
+                        name=unit_id,
+                        rust_type=Uuid,
+                        label="Unit",
+                        options=AsyncResource! {
+                            (
+                                product_name: Option<String> = product_name_signal(),
+                                show_units_set: WriteSignal<bool> = show_units_set
+                            ) -> Result<Vec<(String, String)>, leptos::error::Error> {
+                                let units = product_unit_fetcher(product_name).await?;
+
+                                show_units_set.set(units.is_some());
+                                if let Some(units) = units {
+                                    Ok(
+                                        units
+                                            .into_iter()
+                                            .map(|unit| (unit.full_name_singular, unit.id.to_string()))
+                                            .collect()
+                                    )
+                                } else {
+                                    Ok(vec![])
+                                }
+                            }
+                        },
+                    />
+                </Show>
+
+                <Input
+                    name=amount,
+                    rust_type=u16,
+                    html_type="number",
+                    label="Amount"
+                />
+            }
+        }
+    }
+}
+
+async fn generate_suggest_products(
+    optional_product_name: Option<String>,
+) -> Result<Option<Vec<String>>, leptos::error::Error> {
+    if let Some(product_name) = optional_product_name
+        && !product_name.is_empty()
+    {
+        let products = get_products_by_part_name(product_name).await?;
+        Ok(Some(products.into_iter().map(|prod| prod.name).collect()))
+    } else {
+        Ok(None)
+    }
+}
+
+async fn product_unit_fetcher(
+    optinal_product_name: Option<String>,
+) -> Result<Option<Vec<Unit>>, leptos::error::Error> {
+    if let Some(product_name) = optinal_product_name
+        && !product_name.is_empty()
+    {
+        let value: Option<Product> = {
+            match get_product_by_name(product_name).await {
+                Ok(ok) => Ok::<_, leptos::error::Error>(Some(ok)),
+                Err(err) => match err {
+                    Error::ResponseError(ref response_content) => {
+                        match response_content.status.as_u16() {
+                            404 => Ok(None),
+                            _ => Err(err.into()),
+                        }
+                    }
+                    err => Err(err.into()),
+                },
+            }?
+        };
+
+        if let Some(value) = value {
+            let (_, unit_property) = get_product_unit_by_id(value.id).await?;
+
+            let mut units = Vec::with_capacity(unit_property.units.len());
+            for unit_id in unit_property.units {
+                units.push(get_unit_by_id(unit_id).await?);
+            }
+
+            Ok(Some(units))
+        } else {
+            Ok(None)
+        }
+    } else {
+        Ok(None)
     }
 }
diff --git a/src/components/input_placeholder.rs b/src/components/input_placeholder.rs
index aeef838..99b3196 100644
--- a/src/components/input_placeholder.rs
+++ b/src/components/input_placeholder.rs
@@ -1,27 +1,40 @@
 use leptos::{
     IntoView, component,
+    error::Error,
     html::Input,
-    prelude::{ClassAttribute, ElementChild, GlobalAttributes, NodeRef, NodeRefAttribute},
+    prelude::{
+        ClassAttribute, CollectView, ElementChild, Get, GlobalAttributes, LocalResource, NodeRef,
+        NodeRefAttribute, OnAttribute, OnTargetAttribute, PropAttribute, Set, Show, WriteSignal,
+        signal,
+    },
     view,
 };
+use log::{error, info};
 
 use crate::components::get_id;
 
-
 #[component]
+#[expect(clippy::too_many_lines)]
 pub fn InputPlaceholder(
     input_type: &'static str,
     label: &'static str,
     node_ref: NodeRef<Input>,
     #[prop(default = None)] initial_value: Option<String>,
+    #[prop(default = None)] reactive: Option<WriteSignal<Option<String>>>,
+    #[prop(default = None)] auto_complete: Option<
+        LocalResource<Result<Option<Vec<String>>, Error>>,
+    >,
 ) -> impl IntoView {
     let id = get_id();
 
+    let (autocomplete_signal, autocomplete_set) = signal(String::new());
+
     view! {
         <div class="relative h-14">
             <input
                 id=id.to_string()
                 type=input_type
+                autocomplete="off"
                 class="\
                 absolute \
                 bottom-0 \
@@ -32,7 +45,8 @@ pub fn InputPlaceholder(
                 border-gray-200 \
                 focus:outline-none \
                 h-10 \
-                peer \
+                peer/input \
+                group/input \
                 placeholder-transparent \
                 rounded-t-lg \
                 text-gray-900 \
@@ -41,16 +55,23 @@ pub fn InputPlaceholder(
                 placeholder="sentinel value"
                 node_ref=node_ref
                 value=initial_value
+                on:input:target=move |ev| {
+                    if let Some(signal) = reactive {
+                        signal.set(Some(ev.target().value()));
+                        autocomplete_set.set(ev.target().value());
+                    }
+                }
+                prop:value=autocomplete_signal
             />
 
             // TODO: Reference `var(--tw-border-2)` instead of the `2 px` <2025-10-01>
-            <div class="
+            <div class="\
             absolute \
             bottom-0 \
             h-[2px] \
             w-full \
             bg-gray-300 \
-            peer-focus:bg-indigo-600 \
+            peer-focus/input:bg-indigo-600 \
             " />
 
             <label
@@ -62,18 +83,125 @@ pub fn InputPlaceholder(
                 text-gray-700 \
                 text-sm \
                 transition-all \
-                peer-focus:bottom-10 \
-                peer-focus:left-0 \
-                peer-focus:text-gray-700 \
-                peer-focus:text-sm \
-                peer-placeholder-shown:text-base \
-                peer-placeholder-shown:text-gray-400 \
-                peer-placeholder-shown:bottom-2 \
-                peer-placeholder-shown:left-2 \
+                peer-focus/input:bottom-10 \
+                peer-focus/input:left-0 \
+                peer-focus/input:text-gray-700 \
+                peer-focus/input:text-sm \
+                peer-placeholder-shown/input:text-base \
+                peer-placeholder-shown/input:text-gray-400 \
+                peer-placeholder-shown/input:bottom-2 \
+                peer-placeholder-shown/input:left-2 \
                 "
             >
                 {label}
             </label>
+
+            <Show
+                when=move || {
+                    !autocomplete_signal.get().is_empty()
+                }
+                fallback=move || ()
+            >
+                <div class="\
+                absolute \
+                top-0 \
+                left-0 \
+                invisible \
+                peer-focus/input:visible \
+                in-focus:visible \
+                ">
+                    <div class="\
+                    flex \
+                    flex-row \
+                    g-0 \
+                    ">
+                        // TODO: Reference `var(--tw-border-8)` instead of the `8 px` <2025-10-11>
+                        <div class="w-[8px] h-full" />
+                        <div class="\
+                        flex \
+                        flex-col \
+                        g-0 \
+                        ">
+                            <div class="h-14 w-full peer/div" />
+                            <div class="\
+                            bg-white \
+                            shadow \
+                            outline \
+                            outline-black/5 \
+                            rounded-lg \
+                            z-50 \
+                            p-2 \
+                            visible \
+                            ">
+                                {move || {
+                                    auto_complete
+                                        .map(|auto_complete| {
+                                            provide_auto_completion(
+                                                auto_complete,
+                                                autocomplete_set,
+                                                reactive
+                                            )
+                                        })
+                                }}
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </Show>
         </div>
     }
 }
+
+fn provide_auto_completion(
+    auto_complete: LocalResource<Result<Option<Vec<String>>, Error>>,
+    autocomplete_set: WriteSignal<String>,
+    reactive: Option<WriteSignal<Option<String>>>,
+) -> impl IntoView {
+    match auto_complete.get() {
+        Some(resource_result) => match resource_result {
+            Ok(resource_fetch) => resource_fetch.map(|_| {
+                view! {
+                    <div class="flex flex-col g-1">
+                        {move || {
+                            auto_complete
+                                .get()
+                                .expect("Worked before")
+                                .unwrap()
+                                .unwrap()
+                                .into_iter()
+                                .map(|item| {
+                                    let item2 = item.clone();
+                                    view! {
+                                        <button
+                                            type="button"
+                                            on:click=move |_| {
+                                                autocomplete_set.set(item2.clone());
+                                                reactive
+                                                    .expect(
+                                                    "Should be set, \
+                                                    when autocomplete is used")
+                                                    .set(Some(item2.clone()));
+
+                                                info!("Set autocomplete to {item2}.");
+                                            }
+                                        >
+                                            {item}
+                                        </button>
+                                    }
+                                })
+                                .collect_view()
+                        }}
+                    </div>
+                }
+            }),
+            Err(err) => {
+                error!(
+                    "Error while loading \
+                    autocompletion: {err}"
+                );
+                None
+            }
+        },
+        None => None,
+    }
+}
diff --git a/src/components/mod.rs b/src/components/mod.rs
index 1ee37d5..ca2ac10 100644
--- a/src/components/mod.rs
+++ b/src/components/mod.rs
@@ -7,6 +7,7 @@ pub mod form;
 pub mod icon_p;
 pub mod input_placeholder;
 pub mod select_placeholder;
+pub mod banner;
 
 // Specific
 pub mod buy;
diff --git a/src/components/product_overview.rs b/src/components/product_overview.rs
index d86c04d..777baef 100644
--- a/src/components/product_overview.rs
+++ b/src/components/product_overview.rs
@@ -19,8 +19,9 @@ pub fn ProductOverview() -> impl IntoView {
                 (view! { <IconP icon=icondata_io::IoStorefront text="Buy" /> }, "buy"),
             ]
         >
-            {AsyncFetch!(
-                fetcher = {get_products()}
+            {
+                AsyncFetch! {
+                    fetcher = {get_products()}
                 producer = |products| {
                   let products_num = products.len();
                   let plural_s = if products_num == 1 { "" } else { "s" };
@@ -37,7 +38,8 @@ pub fn ProductOverview() -> impl IntoView {
                       </p>
                   }
             }
-            )}
+                }
+            }
         </Container>
     }
 }
diff --git a/src/components/select_placeholder.rs b/src/components/select_placeholder.rs
index 947931c..2e0f783 100644
--- a/src/components/select_placeholder.rs
+++ b/src/components/select_placeholder.rs
@@ -1,37 +1,24 @@
 use leptos::{
-    IntoView,
-    attr::{AttributeValue, IntoAttributeValue},
-    component,
+    IntoView, component,
+    error::Error,
     html::Select,
     prelude::{
         ClassAttribute, CollectView, ElementChild, GlobalAttributes, NodeRef, NodeRefAttribute,
     },
+    server::LocalResource,
     view,
 };
 
-use crate::components::get_id;
+use crate::components::{async_fetch::AsyncFetch, get_id};
 
 #[component]
-pub fn SelectPlaceholder<T>(
+pub fn SelectPlaceholder(
     label: &'static str,
     node_ref: NodeRef<Select>,
-    options: Vec<(&'static str, T)>,
-) -> impl IntoView
-where
-    T: IntoAttributeValue,
-    <T as IntoAttributeValue>::Output: Send + AttributeValue,
-{
+    options: LocalResource<Result<Vec<(String, String)>, Error>>,
+) -> impl IntoView {
     let id = get_id();
 
-    let options = options
-        .into_iter()
-        .map(|(label, value)| {
-            view! {
-                <option value=value>{label}</option>
-            }
-        })
-        .collect_view();
-
     view! {
         <div class="relative h-14">
             <select
@@ -54,7 +41,18 @@ where
                 "
                 node_ref=node_ref
             >
-                {options}
+                {move || AsyncFetch! {
+                    @map_error_in_producer
+                    from_resource = options
+                    producer = |options| {
+                        options
+                            .into_iter()
+                            .map(|(label, value)| {
+                                view! { <option value=value>{label}</option> }
+                            })
+                            .collect_view()
+                    }
+                }}
             </select>
 
             // TODO: Reference `var(--tw-border-2)` instead of the `2 px` <2025-10-01>