update the dioxus cratesio readme, add many more examples (#5501)
* update the dioxus cratesio readme * fix example * fix header * add a bunch more examples
This commit is contained in:
1 parent
74e7d20f80
commit
cd54f5953b
30 files changed
+1403
-235
No files matched your search
@@ -0,0 +1,45 @@
|
||||
//! Checkboxes and radio groups.
|
||||
//!
|
||||
//! Checkboxes expose their checked state through the FormEvent — read `evt.checked()`.
|
||||
//! Radio groups share a `name` attribute so only one option can be selected at a time;
|
||||
//! the selected value comes through `evt.value()`.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut subscribed = use_signal(|| false);
|
||||
let mut color = use_signal(|| "red".to_string());
|
||||
|
||||
rsx! {
|
||||
h2 { "Checkbox" }
|
||||
label {
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
checked: subscribed(),
|
||||
oninput: move |evt| subscribed.set(evt.checked()),
|
||||
}
|
||||
" Subscribe to updates"
|
||||
}
|
||||
p { "Subscribed: {subscribed}" }
|
||||
|
||||
h2 { "Radio group" }
|
||||
// Radio inputs sharing a `name` are mutually exclusive
|
||||
for value in ["red", "green", "blue"] {
|
||||
label {
|
||||
input {
|
||||
r#type: "radio",
|
||||
name: "color",
|
||||
value,
|
||||
checked: color() == value,
|
||||
oninput: move |evt| color.set(evt.value()),
|
||||
}
|
||||
" {value}"
|
||||
}
|
||||
}
|
||||
p { color: "{color}", "Chosen color: {color}" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//! Accepting children on a component.
|
||||
//!
|
||||
//! Any component can take a `children: Element` prop. Anything nested inside the component
|
||||
//! in `rsx!` is passed in as `children`. This is how you build reusable layout and container
|
||||
//! components like cards, modals, or page shells.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
Card {
|
||||
h2 { "A card" }
|
||||
p { "Any rsx! can go inside — including other components." }
|
||||
}
|
||||
|
||||
Card {
|
||||
p { "A second card with different children." }
|
||||
button { "Click me" }
|
||||
}
|
||||
|
||||
Section { title: "Reusable layout",
|
||||
p { "The section component takes both a title prop and children." }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Card(children: Element) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
border: "1px solid #ccc",
|
||||
border_radius: "8px",
|
||||
padding: "12px",
|
||||
margin: "8px 0",
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Section(title: String, children: Element) -> Element {
|
||||
rsx! {
|
||||
section {
|
||||
h3 { "{title}" }
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//! Defining and using components.
|
||||
//!
|
||||
//! A component is just a function annotated with `#[component]` that returns an `Element`.
|
||||
//! Its arguments are its props — Dioxus generates a builder so you can construct the
|
||||
//! component inside `rsx!` with a struct-like syntax.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
h1 { "Greetings" }
|
||||
Greeting { name: "Alice" }
|
||||
Greeting { name: "Bob", excited: true }
|
||||
Greeting { name: "Charlie", excited: false }
|
||||
|
||||
h2 { "User cards" }
|
||||
UserCard { name: "Ada Lovelace", role: "Mathematician" }
|
||||
UserCard { name: "Grace Hopper", role: "Computer Scientist" }
|
||||
}
|
||||
}
|
||||
|
||||
// Components must start with an uppercase letter.
|
||||
// Props are taken as named arguments — `name` is required, and `excited` has a default.
|
||||
#[component]
|
||||
fn Greeting(name: String, #[props(default)] excited: bool) -> Element {
|
||||
let punctuation = if excited { "!" } else { "." };
|
||||
rsx! {
|
||||
p { "Hello, {name}{punctuation}" }
|
||||
}
|
||||
}
|
||||
|
||||
// Components can take any Clone + PartialEq type as a prop.
|
||||
#[component]
|
||||
fn UserCard(name: String, role: String) -> Element {
|
||||
rsx! {
|
||||
div { border: "1px solid #ccc", padding: "8px", margin: "4px",
|
||||
strong { "{name}" }
|
||||
" — "
|
||||
em { "{role}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//! Conditional rendering.
|
||||
//!
|
||||
//! `if`, `else`, and `match` all work directly inside `rsx!`. You can also return different
|
||||
//! `rsx!` blocks early from a component function. Elements outside of a conditional will
|
||||
//! render normally — only the branched portion swaps as state changes.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
enum Status {
|
||||
Loading,
|
||||
Ready,
|
||||
Error,
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut logged_in = use_signal(|| false);
|
||||
let mut status = use_signal(|| Status::Loading);
|
||||
|
||||
rsx! {
|
||||
h1 { "Conditional rendering" }
|
||||
|
||||
// `if/else` chains render the matching branch
|
||||
if logged_in() {
|
||||
p { "Welcome back!" }
|
||||
} else {
|
||||
p { "Please log in." }
|
||||
}
|
||||
button { onclick: move |_| logged_in.toggle(), "Toggle login" }
|
||||
|
||||
// `match` works too — each arm returns rsx!
|
||||
div {
|
||||
match status() {
|
||||
Status::Loading => rsx! { p { "⏳ Loading..." } },
|
||||
Status::Ready => rsx! { p { "✅ Ready!" } },
|
||||
Status::Error => rsx! { p { "❌ Something went wrong." } },
|
||||
}
|
||||
}
|
||||
button { onclick: move |_| status.set(Status::Loading), "Loading" }
|
||||
button { onclick: move |_| status.set(Status::Ready), "Ready" }
|
||||
button { onclick: move |_| status.set(Status::Error), "Error" }
|
||||
|
||||
// `if` without an `else` is also valid — nothing renders when false
|
||||
if logged_in() && status() == Status::Ready {
|
||||
p { "You are logged in and the app is ready." }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//! The classic counter — the "hello world" of reactive UI.
|
||||
//!
|
||||
//! `use_signal` creates a reactive value. Whenever the signal changes, any part of the UI
|
||||
//! that reads it will re-render automatically. Signals are copy-cheap, so you can freely
|
||||
//! move them into event handlers and closures.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
|
||||
rsx! {
|
||||
h1 { "Count: {count}" }
|
||||
button { onclick: move |_| count += 1, "Increment" }
|
||||
button { onclick: move |_| count -= 1, "Decrement" }
|
||||
button { onclick: move |_| count.set(0), "Reset" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//! Applying classes and styles based on state.
|
||||
//!
|
||||
//! Any attribute can interpolate signals and expressions using `"{value}"` formatting.
|
||||
//! For `class:`, you can also build the class string inline with if/else, and individual
|
||||
//! CSS properties can be set as named attributes like `color:` or `background_color:`.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut is_active = use_signal(|| false);
|
||||
let mut size = use_signal(|| 16);
|
||||
|
||||
// Build a class string from state
|
||||
let classes = if is_active() { "btn active" } else { "btn" };
|
||||
|
||||
rsx! {
|
||||
// CSS properties are plain named attributes — snake_case in Rust, kebab-case in CSS
|
||||
style { {STYLE} }
|
||||
|
||||
button {
|
||||
class: classes,
|
||||
onclick: move |_| is_active.toggle(),
|
||||
"Toggle active (currently {is_active})"
|
||||
}
|
||||
|
||||
// Or compute the class string ahead of time for anything more complex
|
||||
div {
|
||||
class: if is_active() { "box highlight" } else { "box" },
|
||||
"This box changes class with the button."
|
||||
}
|
||||
|
||||
hr {}
|
||||
|
||||
// Style values can be driven directly by signals
|
||||
p {
|
||||
font_size: "{size}px",
|
||||
color: if size() > 24 { "crimson" } else { "black" },
|
||||
"Font size: {size}px"
|
||||
}
|
||||
button { onclick: move |_| size += 2, "Bigger" }
|
||||
button { onclick: move |_| size -= 2, "Smaller" }
|
||||
}
|
||||
}
|
||||
|
||||
const STYLE: &str = r#"
|
||||
.btn { padding: 6px 12px; border: 1px solid #888; border-radius: 4px; }
|
||||
.btn.active { background: #4CAF50; color: white; border-color: #2E7D32; }
|
||||
.box { padding: 12px; margin-top: 12px; border: 1px dashed #999; }
|
||||
.box.highlight { background: #fff3cd; border-color: #d39e00; }
|
||||
"#;
|
||||
@@ -0,0 +1,45 @@
|
||||
//! Passing event handlers as props.
|
||||
//!
|
||||
//! Components can accept callbacks using `EventHandler<T>`, which is Dioxus's equivalent
|
||||
//! of React's `onClick` prop or a closure. Call the handler with `.call(value)` from the
|
||||
//! child; the parent decides what happens when the event fires.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut log = use_signal(Vec::<String>::new);
|
||||
|
||||
rsx! {
|
||||
h1 { "Custom event handlers" }
|
||||
|
||||
// The parent owns the handler — the child just fires it
|
||||
FancyButton {
|
||||
label: "Say hi",
|
||||
onpress: move |name: String| log.write().push(format!("Hi, {name}!")),
|
||||
}
|
||||
FancyButton {
|
||||
label: "Say bye",
|
||||
onpress: move |name: String| log.write().push(format!("Bye, {name}!")),
|
||||
}
|
||||
|
||||
ul {
|
||||
for entry in log.iter() {
|
||||
li { "{entry}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn FancyButton(label: String, onpress: EventHandler<String>) -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
onclick: move |_| onpress.call(label.clone()),
|
||||
"{label}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//! Handling form submission and deserializing into a struct.
|
||||
//!
|
||||
//! `evt.parsed_values::<T>()` takes the form's named inputs and deserializes them straight
|
||||
//! into your own type via serde. This skips the chore of pulling each field out of the
|
||||
//! values map by hand — just define a struct whose fields match the `name` attributes,
|
||||
//! derive `Deserialize`, and you're done.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use serde::Deserialize;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
// Field names must match the `name` attribute on each input
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct Signup {
|
||||
username: String,
|
||||
email: String,
|
||||
role: String,
|
||||
// Checkboxes are present in the form data only when checked, so model optional fields
|
||||
// with Option<String>. Use `#[serde(default)]` so the field doesn't fail to deserialize
|
||||
// when the input is missing.
|
||||
#[serde(default)]
|
||||
newsletter: Option<String>,
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut submitted = use_signal(|| None::<Signup>);
|
||||
|
||||
rsx! {
|
||||
h1 { "Sign up" }
|
||||
|
||||
form {
|
||||
onsubmit: move |evt| {
|
||||
// prevent_default stops the browser from navigating the page on submit
|
||||
evt.prevent_default();
|
||||
|
||||
// Deserialize directly into our struct
|
||||
match evt.parsed_values::<Signup>() {
|
||||
Ok(signup) => submitted.set(Some(signup)),
|
||||
Err(err) => eprintln!("failed to parse form: {err}"),
|
||||
}
|
||||
},
|
||||
|
||||
div {
|
||||
label { r#for: "username", "Username: " }
|
||||
input { r#type: "text", id: "username", name: "username", required: true }
|
||||
}
|
||||
|
||||
div {
|
||||
label { r#for: "email", "Email: " }
|
||||
input { r#type: "email", id: "email", name: "email", required: true }
|
||||
}
|
||||
|
||||
div {
|
||||
label { r#for: "role", "Role: " }
|
||||
select { id: "role", name: "role",
|
||||
option { value: "developer", "Developer" }
|
||||
option { value: "designer", "Designer" }
|
||||
option { value: "manager", "Manager" }
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
label { r#for: "newsletter",
|
||||
input { r#type: "checkbox", id: "newsletter", name: "newsletter", value: "yes" }
|
||||
" Subscribe to newsletter"
|
||||
}
|
||||
}
|
||||
|
||||
button { r#type: "submit", "Submit" }
|
||||
}
|
||||
|
||||
if let Some(signup) = submitted() {
|
||||
h2 { "Welcome, {signup.username}!" }
|
||||
ul {
|
||||
li { "Email: {signup.email}" }
|
||||
li { "Role: {signup.role}" }
|
||||
li { "Newsletter: {signup.newsletter.is_some()}" }
|
||||
}
|
||||
details {
|
||||
summary { "Raw struct" }
|
||||
pre { "{signup:#?}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//! Controlled text inputs.
|
||||
//!
|
||||
//! To read text from an input, attach an `oninput` handler and call `evt.value()` to get the
|
||||
//! current value as a `String`. Binding the `value:` attribute to a signal makes the input
|
||||
//! "controlled" — the signal is the single source of truth, so clearing it clears the input.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut name = use_signal(String::new);
|
||||
let mut message = use_signal(|| "Type something below.".to_string());
|
||||
|
||||
rsx! {
|
||||
h1 { "Hello, {name}!" }
|
||||
|
||||
input {
|
||||
r#type: "text",
|
||||
placeholder: "Your name",
|
||||
// Binding `value` to a signal makes this a controlled input
|
||||
value: "{name}",
|
||||
oninput: move |evt| name.set(evt.value()),
|
||||
}
|
||||
|
||||
button {
|
||||
onclick: move |_| name.set(String::new()),
|
||||
"Clear"
|
||||
}
|
||||
|
||||
hr {}
|
||||
|
||||
p { "{message}" }
|
||||
textarea {
|
||||
rows: 3,
|
||||
cols: 40,
|
||||
value: "{message}",
|
||||
oninput: move |evt| message.set(evt.value()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//! Rendering lists.
|
||||
//!
|
||||
//! Use a `for` loop inside `rsx!` to render a list from an iterator. When list items can
|
||||
//! be reordered, added, or removed, provide a stable `key` so Dioxus can efficiently
|
||||
//! reuse existing elements rather than re-rendering them all.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut items = use_signal(|| {
|
||||
vec![
|
||||
"apple".to_string(),
|
||||
"banana".to_string(),
|
||||
"cherry".to_string(),
|
||||
]
|
||||
});
|
||||
let mut next_id = use_signal(|| 0);
|
||||
|
||||
rsx! {
|
||||
h1 { "Grocery list" }
|
||||
|
||||
button {
|
||||
onclick: move |_| {
|
||||
let n = next_id();
|
||||
next_id += 1;
|
||||
items.push(format!("item {n}"));
|
||||
},
|
||||
"Add item"
|
||||
}
|
||||
button {
|
||||
onclick: move |_| { items.pop(); },
|
||||
"Remove last"
|
||||
}
|
||||
|
||||
ul {
|
||||
// The `key` is a stable identifier used to match up items across renders.
|
||||
// Here we use the item's text, but in real apps prefer a database id.
|
||||
for item in items.iter() {
|
||||
li { key: "{item}", "{item}" }
|
||||
}
|
||||
}
|
||||
|
||||
// You can also use iterator adapters with .map() for anything more complex than a for loop
|
||||
p { "Uppercased:" }
|
||||
ul {
|
||||
{items.iter().map(|item| rsx! { li { "{item.to_uppercase()}" } })}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//! Setting the page title.
|
||||
//!
|
||||
//! The `Title` component renders a `<title>` tag into the document head. On the web it
|
||||
//! sets the browser tab title; on desktop it becomes the window title. Because it's a
|
||||
//! regular component, its contents can be driven by signals and update live.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut unread = use_signal(|| 0);
|
||||
|
||||
rsx! {
|
||||
// The title updates every time the signal changes
|
||||
Title {
|
||||
if unread() > 0 { "({unread}) Inbox" } else { "Inbox" }
|
||||
}
|
||||
|
||||
h1 { "Inbox" }
|
||||
p { "Unread messages: {unread}" }
|
||||
button { onclick: move |_| unread += 1, "New message" }
|
||||
button { onclick: move |_| unread.set(0), "Mark all read" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//! Styling driven by state.
|
||||
//!
|
||||
//! Style attributes are interpolated the same way text is — read from signals inside `"{...}"`
|
||||
//! or pass a conditional expression directly. The element re-renders only when the read
|
||||
//! signals change, so you can freely animate colors, sizes, and transforms this way.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut hue = use_signal(|| 200.0_f32);
|
||||
let mut size = use_signal(|| 100.0_f32);
|
||||
let mut rotated = use_signal(|| false);
|
||||
|
||||
rsx! {
|
||||
h1 { "Dynamic styling" }
|
||||
|
||||
div {
|
||||
width: "{size}px",
|
||||
height: "{size}px",
|
||||
background_color: "hsl({hue}, 80%, 60%)",
|
||||
transition: "transform 300ms ease",
|
||||
transform: if rotated() { "rotate(45deg)" } else { "rotate(0deg)" },
|
||||
margin: "20px 0",
|
||||
}
|
||||
|
||||
div {
|
||||
label { "Hue: {hue:.0}" }
|
||||
input {
|
||||
r#type: "range",
|
||||
min: "0",
|
||||
max: "360",
|
||||
value: "{hue}",
|
||||
oninput: move |evt| {
|
||||
if let Ok(v) = evt.value().parse::<f32>() {
|
||||
hue.set(v);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
label { "Size: {size:.0}px" }
|
||||
input {
|
||||
r#type: "range",
|
||||
min: "40",
|
||||
max: "300",
|
||||
value: "{size}",
|
||||
oninput: move |evt| {
|
||||
if let Ok(v) = evt.value().parse::<f32>() {
|
||||
size.set(v);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
button { onclick: move |_| rotated.toggle(), "Toggle rotation" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//! Setting a favicon.
|
||||
//!
|
||||
//! Use `document::Link` with `rel: "icon"` to point the browser at a favicon. Combined with
|
||||
//! `asset!`, the favicon is bundled into the app and served with the same path-mangling
|
||||
//! and cache-busting as any other asset.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
const FAVICON: Asset = asset!("/examples/assets/logo.png");
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
// Link is a generic head element — for a favicon set rel="icon"
|
||||
document::Link { rel: "icon", href: FAVICON }
|
||||
|
||||
h1 { "Check the browser tab!" }
|
||||
p { "The favicon is loaded from examples/assets/logo.png." }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//! Loading fonts — from a CDN and bundled with the app.
|
||||
//!
|
||||
//! Two common ways to get a custom typeface onto the page:
|
||||
//!
|
||||
//! 1. Point `Stylesheet` at a hosted CSS file like Google Fonts — the browser fetches the
|
||||
//! font files on demand.
|
||||
//! 2. Bundle a font file into the app with `asset!`, then declare an `@font-face` rule
|
||||
//! that points at it. This avoids the network round-trip and makes the font available
|
||||
//! offline.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
// A local font, bundled into the app. The asset macro fingerprints and copies the file
|
||||
// into the final build; at runtime `INTER` formats to the public URL.
|
||||
const INTER: Asset = asset!("/examples/assets/Inter-Regular.woff2");
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
// Approach 1 — request a stylesheet from the Google Fonts CDN
|
||||
Stylesheet { href: "https://fonts.googleapis.com/css2?family=Lobster&family=Roboto+Mono&display=swap" }
|
||||
|
||||
// Approach 2 — register a bundled font with an inline @font-face rule.
|
||||
// `{INTER}` interpolates to the asset's resolved URL at runtime.
|
||||
style { "@font-face {{ font-family: 'Inter'; src: url('{INTER}') format('woff2'); font-display: swap; }}" }
|
||||
|
||||
h1 { font_family: "'Lobster', cursive", font_size: "48px",
|
||||
"From Google Fonts"
|
||||
}
|
||||
p { font_family: "'Roboto Mono', monospace",
|
||||
"This paragraph also comes from the CDN."
|
||||
}
|
||||
|
||||
hr {}
|
||||
|
||||
h2 { font_family: "'Inter', sans-serif", "Bundled Inter font" }
|
||||
p { font_family: "'Inter', sans-serif",
|
||||
"This paragraph uses Inter, shipped with the app — no network request needed."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//! Inline styles.
|
||||
//!
|
||||
//! For quick one-off styling, every CSS property is available as an attribute on HTML
|
||||
//! elements — use snake_case in Rust (`background_color`) for the kebab-case CSS property
|
||||
//! (`background-color`). You can also set the raw `style:` attribute with a full string.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
// Each CSS property is just a named attribute
|
||||
div {
|
||||
background_color: "#1e3a8a",
|
||||
color: "white",
|
||||
padding: "20px",
|
||||
border_radius: "8px",
|
||||
font_family: "system-ui, sans-serif",
|
||||
|
||||
h1 { "Inline styling" }
|
||||
p { "Property names are snake_case here, kebab-case in real CSS." }
|
||||
}
|
||||
|
||||
// The raw `style` attribute accepts any CSS string
|
||||
div {
|
||||
style: "margin-top: 12px; border: 2px dashed tomato; padding: 10px;",
|
||||
"Use `style:` when you want a plain CSS string."
|
||||
}
|
||||
|
||||
// Interpolation works too — great for values driven by Rust expressions
|
||||
for i in 1..=5 {
|
||||
span {
|
||||
display: "inline-block",
|
||||
width: "32px",
|
||||
height: "32px",
|
||||
margin: "4px",
|
||||
background_color: "hsl({i * 60}, 70%, 55%)",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
//! This example shows how to add metadata to the page with the Meta component
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
// You can use the Meta component to render a meta tag into the head of the page
|
||||
// Meta tags are useful to provide information about the page to search engines and social media sites
|
||||
// This example sets up meta tags for the open graph protocol for social media previews
|
||||
Meta { property: "og:title", content: "My Site" }
|
||||
Meta { property: "og:type", content: "website" }
|
||||
Meta { property: "og:url", content: "https://www.example.com" }
|
||||
Meta { property: "og:image", content: "https://example.com/image.jpg" }
|
||||
Meta { name: "description", content: "My Site is a site" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//! Adding a script to the document head.
|
||||
//!
|
||||
//! `document::Script` injects a `<script>` tag into the page. Use it to embed an inline
|
||||
//! snippet via children, or load an external script by passing a `src`. Scripts are
|
||||
//! deduplicated by their `src`, so mounting the same script from multiple components is safe.
|
||||
//!
|
||||
//! Prefer `Script` over `link {}` for better integration with SSR and pre-rendering.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
// Inline script — children must be a single text node
|
||||
document::Script { "console.log('Hello from an inline script!');" }
|
||||
|
||||
// External script, loaded with `defer` so it runs after parsing
|
||||
document::Script { src: "https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js", defer: true }
|
||||
|
||||
h1 { "Script example" }
|
||||
p { "Open your browser's console to see the inline script's output." }
|
||||
|
||||
button {
|
||||
onclick: move |_| {
|
||||
// Call out to the confetti library loaded above
|
||||
document::eval("confetti && confetti();");
|
||||
},
|
||||
"🎉 Confetti"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//! Linking a CSS stylesheet.
|
||||
//!
|
||||
//! `Stylesheet` renders a `<link rel="stylesheet">` into the document head. Pairing it with
|
||||
//! the `asset!` macro is the most common way to style a Dioxus app: the CSS file is bundled,
|
||||
//! fingerprinted for cache-busting, and can be hot-reloaded during development.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
const STYLE: Asset = asset!("/examples/assets/stylesheet.css");
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
// Placing the Stylesheet anywhere in the tree is fine — it's collected into <head>
|
||||
Stylesheet { href: STYLE }
|
||||
|
||||
h1 { "Styled with a CSS file" }
|
||||
p { "All styling comes from stylesheet.css, loaded via the asset! macro." }
|
||||
button { "A styled button" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//! Writing your own hook.
|
||||
//!
|
||||
//! A hook is just a function that calls other hooks. By convention it starts with `use_`
|
||||
//! and returns whatever state handle the caller needs. Extracting hooks is how you reuse
|
||||
//! stateful logic across components — like a counter, a timer, or a data fetcher.
|
||||
//!
|
||||
//! Hooks rely on being called in the same order every render, so never call them inside
|
||||
//! `if`, `match`, or loops.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
// Each call to our custom hook produces an independent counter
|
||||
let mut counter_a = use_counter(0);
|
||||
let mut counter_b = use_counter(100);
|
||||
|
||||
// And a more complex hook that exposes multiple actions
|
||||
let (name, set_name, reverse) = use_reversible_name("Ada");
|
||||
|
||||
rsx! {
|
||||
h2 { "Two counters from one hook" }
|
||||
p { "A: {counter_a}" }
|
||||
button { onclick: move |_| counter_a += 1, "Increment A" }
|
||||
|
||||
p { "B: {counter_b}" }
|
||||
button { onclick: move |_| counter_b += 1, "Increment B" }
|
||||
|
||||
h2 { "Reversible name" }
|
||||
input {
|
||||
value: "{name}",
|
||||
oninput: move |evt| set_name.call(evt.value()),
|
||||
}
|
||||
button { onclick: move |_| reverse.call(()), "Reverse" }
|
||||
p { "Name: {name}" }
|
||||
}
|
||||
}
|
||||
|
||||
// A minimal hook — returns a signal initialized with a value
|
||||
fn use_counter(initial: i32) -> Signal<i32> {
|
||||
use_signal(|| initial)
|
||||
}
|
||||
|
||||
// Hooks can return any combination of signals and callbacks
|
||||
fn use_reversible_name(initial: &'static str) -> (Signal<String>, Callback<String>, Callback<()>) {
|
||||
let mut name = use_signal(|| initial.to_string());
|
||||
|
||||
// `use_callback` memoizes a closure so children don't think it changed every render
|
||||
let set_name = use_callback(move |value: String| name.set(value));
|
||||
let reverse = use_callback(move |_| {
|
||||
let reversed = name.read().chars().rev().collect::<String>();
|
||||
name.set(reversed);
|
||||
});
|
||||
|
||||
(name, set_name, reverse)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//! Sharing state between sibling components by lifting it up.
|
||||
//!
|
||||
//! When two components need to read or edit the same value, put the signal in their
|
||||
//! common parent and pass it down as a prop. This keeps the data flow obvious: the parent
|
||||
//! owns the state, the children receive a `Signal<T>` handle, and mutations show up
|
||||
//! everywhere the signal is read.
|
||||
//!
|
||||
//! For state that needs to be shared across many layers, reach for `use_context_provider`
|
||||
//! instead — see `context_api.rs`.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
// The parent owns the signal; both children share it
|
||||
let counter = use_signal(|| 0);
|
||||
|
||||
rsx! {
|
||||
h1 { "Two views of the same counter" }
|
||||
Controls { counter }
|
||||
Display { counter }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Controls(mut counter: Signal<i32>) -> Element {
|
||||
rsx! {
|
||||
button { onclick: move |_| counter += 1, "Increment" }
|
||||
button { onclick: move |_| counter -= 1, "Decrement" }
|
||||
button { onclick: move |_| counter.set(0), "Reset" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Display(counter: Signal<i32>) -> Element {
|
||||
rsx! {
|
||||
p { "Count is: {counter}" }
|
||||
p {
|
||||
if counter() > 0 { "Positive" }
|
||||
else if counter() < 0 { "Negative" }
|
||||
else { "Zero" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//! Passing a read-only signal to a child component.
|
||||
//!
|
||||
//! `ReadSignal<T>` is a signal that can only be read, not written. Taking it as a prop is
|
||||
//! the idiomatic way to tell a child "you can subscribe to this value, but you can't change
|
||||
//! it." Any `Signal<T>` or `Memo<T>` coerces into a `ReadSignal<T>` automatically, so the
|
||||
//! parent passes them in unchanged.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
let doubled = use_memo(move || count() * 2);
|
||||
|
||||
rsx! {
|
||||
h1 { "Parent" }
|
||||
button { onclick: move |_| count += 1, "Increment" }
|
||||
|
||||
// Both Signal and Memo implement Into<ReadSignal<T>>
|
||||
Display { label: "Count", value: count }
|
||||
Display { label: "Doubled", value: doubled }
|
||||
|
||||
// You can also pass a plain value — it'll be wrapped automatically
|
||||
Display { label: "Constant", value: 42 }
|
||||
}
|
||||
}
|
||||
|
||||
// Taking `ReadSignal<i32>` means the child can subscribe but can't mutate the source
|
||||
#[component]
|
||||
fn Display(label: String, value: ReadSignal<i32>) -> Element {
|
||||
rsx! {
|
||||
p { "{label}: {value}" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//! A signal that holds a struct.
|
||||
//!
|
||||
//! Signals don't care what they hold — you can put any `'static` type inside. To mutate
|
||||
//! a field on a struct signal, call `.write()` and the guard derefs to the inner value.
|
||||
//! Any read of the signal in the UI will re-render when you drop the write guard.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct User {
|
||||
name: String,
|
||||
age: u32,
|
||||
admin: bool,
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut user = use_signal(|| User {
|
||||
name: "Ada".to_string(),
|
||||
age: 36,
|
||||
admin: false,
|
||||
});
|
||||
|
||||
rsx! {
|
||||
h1 { "User profile" }
|
||||
p { "Name: {user.read().name}" }
|
||||
p { "Age: {user.read().age}" }
|
||||
p { "Admin: {user.read().admin}" }
|
||||
|
||||
button {
|
||||
// .write() gives a mutable guard — assign to a single field
|
||||
onclick: move |_| user.write().age += 1,
|
||||
"Happy birthday!"
|
||||
}
|
||||
button {
|
||||
onclick: move |_| {
|
||||
let mut guard = user.write();
|
||||
guard.admin = !guard.admin;
|
||||
},
|
||||
"Toggle admin"
|
||||
}
|
||||
|
||||
input {
|
||||
value: "{user.read().name}",
|
||||
oninput: move |evt| user.write().name = evt.value(),
|
||||
}
|
||||
|
||||
// Replacing the whole value works too
|
||||
button {
|
||||
onclick: move |_| user.set(User {
|
||||
name: "Grace".to_string(),
|
||||
age: 85,
|
||||
admin: true,
|
||||
}),
|
||||
"Reset to Grace"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//! Running side effects when state changes.
|
||||
//!
|
||||
//! `use_effect` runs a closure after every render where one of the signals it reads has
|
||||
//! changed. It's the escape hatch for talking to code outside of Dioxus — logging, syncing
|
||||
//! to `localStorage`, updating the `document.title`, tweaking imperative APIs, and so on.
|
||||
//!
|
||||
//! For pure derivations of other signals, reach for `use_memo` instead — effects are meant
|
||||
//! for side effects, not for computing values used in the UI.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
let mut name = use_signal(|| "world".to_string());
|
||||
|
||||
// Runs after every render where `count` changes — use this for side effects
|
||||
use_effect(move || {
|
||||
println!("count is now {count}");
|
||||
});
|
||||
|
||||
// An effect can read multiple signals. It re-runs whenever any of them change.
|
||||
use_effect(move || {
|
||||
println!("greeting changed: hello, {name} — count is {count}");
|
||||
});
|
||||
|
||||
rsx! {
|
||||
h1 { "Hello, {name}! ({count})" }
|
||||
|
||||
button { onclick: move |_| count += 1, "Increment count" }
|
||||
input {
|
||||
value: "{name}",
|
||||
oninput: move |evt| name.set(evt.value()),
|
||||
}
|
||||
p { "Check your terminal — each change fires an effect." }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//! Deriving values with `use_memo`.
|
||||
//!
|
||||
//! `use_memo` caches the result of a closure and re-runs it only when one of the signals
|
||||
//! it reads changes. It's how you compute values derived from state without doing the work
|
||||
//! on every render, and without introducing another `use_signal` that you have to keep
|
||||
//! in sync manually.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut first = use_signal(|| "Ada".to_string());
|
||||
let mut last = use_signal(|| "Lovelace".to_string());
|
||||
|
||||
// Recomputes only when `first` or `last` change
|
||||
let full_name = use_memo(move || format!("{first} {last}"));
|
||||
|
||||
// Memos compose — derive from other memos or signals
|
||||
let initials = use_memo(move || {
|
||||
let name = full_name();
|
||||
name.split_whitespace()
|
||||
.filter_map(|w| w.chars().next())
|
||||
.collect::<String>()
|
||||
});
|
||||
|
||||
rsx! {
|
||||
h1 { "{full_name}" }
|
||||
p { "Initials: {initials}" }
|
||||
|
||||
label { "First: " }
|
||||
input { value: "{first}", oninput: move |e| first.set(e.value()) }
|
||||
|
||||
label { "Last: " }
|
||||
input { value: "{last}", oninput: move |e| last.set(e.value()) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//! Signals that hold a collection.
|
||||
//!
|
||||
//! `Signal<Vec<T>>` is the workhorse for dynamic lists. Dioxus exposes `push`, `pop`,
|
||||
//! `remove`, `clear`, and `retain` directly on the signal so you don't have to call
|
||||
//! `.write()` for the common cases. For anything else, `.write()` returns a guard that
|
||||
//! derefs to the underlying `Vec`.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut todos = use_signal(|| vec!["learn dioxus".to_string(), "build an app".to_string()]);
|
||||
let mut draft = use_signal(String::new);
|
||||
|
||||
rsx! {
|
||||
h1 { "Todo list" }
|
||||
|
||||
input {
|
||||
placeholder: "New todo...",
|
||||
value: "{draft}",
|
||||
oninput: move |evt| draft.set(evt.value()),
|
||||
}
|
||||
button {
|
||||
onclick: move |_| {
|
||||
if !draft.read().is_empty() {
|
||||
todos.push(draft());
|
||||
draft.set(String::new());
|
||||
}
|
||||
},
|
||||
"Add"
|
||||
}
|
||||
|
||||
ul {
|
||||
for (index, todo) in todos.read().iter().enumerate() {
|
||||
li { key: "{index}",
|
||||
"{todo} "
|
||||
button {
|
||||
onclick: move |_| { todos.remove(index); },
|
||||
"×"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button { onclick: move |_| todos.clear(), "Clear all" }
|
||||
button {
|
||||
// `.retain` accepts a predicate — here we drop any todo containing "x"
|
||||
onclick: move |_| todos.retain(|t| !t.contains('x')),
|
||||
"Drop todos with 'x'"
|
||||
}
|
||||
button {
|
||||
// For anything custom, `.write()` gives a mutable guard to the Vec
|
||||
onclick: move |_| todos.write().sort(),
|
||||
"Sort alphabetically"
|
||||
}
|
||||
|
||||
p { "{todos.len()} todo(s)" }
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,21 @@
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
background: #fafafa;
|
||||
color: #222;
|
||||
max-width: 640px;
|
||||
margin: 40px auto;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
h1 { color: #1e3a8a; }
|
||||
|
||||
button {
|
||||
padding: 6px 14px;
|
||||
border: 1px solid #1e3a8a;
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
color: #1e3a8a;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover { background: #1e3a8a; color: white; }
|
||||
Reference in new issue
Block a user