Component
A component is any Rust struct that implements the Component trait. Components are the building blocks of Tidos, they encapsulate HTML structure, styles, and logic.
The view! Macro
view! is the templating macro. Use it inside the to_render function of the Component trait to write HTML with Rust expressions. All text between tags must be in {}. Attribute values use that same {} syntax — literals, formatted strings, or any expression — and a : prefix turns an attribute into a toggle that is either present or absent (like disabled), rather than set to a string value:
1let count = 42_usize;2let name = "Alice";3let is_loading = true;45view! {6 // --- Text content ---78 // Static text9 <p>{"Hello world"}</p>1011 // Formatted, with {} placeholders12 <p>{"Hello {}, you have {} items.", name, count}</p>1314 // Any Rust expression15 <p>{count.to_string()}</p>1617 // Raw HTML (not sanitized) — use with care!18 <div>@html{"<em>italic</em>"}</div>1920 // --- Attributes share the exact same content syntax ---2122 // Static value23 <span class={"rounded"}></span>2425 // Formatted value (here combined with scoped_css!)26 <span class={"rounded {}", scoped_css!("./mod.css")}></span>2728 // Any Rust expression29 <div data-count={count.to_string()}></div>3031 // Toggle attribute: present or absent, like `disabled`.32 // `:disabled` reads a bool; `disabled={true}` would render disabled="true" instead.33 <button :disabled={is_loading}>{"Save"}</button>34}
The Component Trait
Implement Component on any struct. The to_render method receives a mutable Page reference so child components can inject CSS or head elements:
1use tidos::{view, Component, Page};23pub struct Alert {4 pub message: String,5 pub kind: String,6}78impl Component for Alert {9 fn to_render(&self, page: &mut Page) {10 view! {11 <div class="alert" data-kind={&self.kind}>12 <p>{&self.message}</p>13 </div>14 }15 }16}
Control Tags
Control tags are special blocks inside view! that map directly to Rust control flow.
Conditions
1let logged_in = true;2let is_admin = false;34view! {5 {#if logged_in && is_admin}6 <span>{"Admin Panel"}</span>7 {:else if logged_in}8 <span>{"Dashboard"}</span>9 {:else}10 <a href="/login">{"Sign in"}</a>11 {/if}12}
Loops
1let fruits = vec!["apple", "banana", "cherry"];23view! {4 <ul>5 {#for fruit in &fruits}6 <li>{fruit}</li>7 {/for}8 </ul>9}
Pattern Matching
1enum Status { Active, Banned, Guest }2let status = Status::Active;34view! {5 {#match status}6 {:case Status::Active}7 <span data-status="active">{"Active"}</span>8 {:case Status::Banned}9 <span data-status="banned">{"Banned"}</span>10 {:case _}11 <span data-status="guest">{"Guest"}</span>12 {/match}13}
Slots
Named slots let a parent pass rendered HTML content into a child component. Use {#slot:name}...{/slot} in the parent and @slot{self.name} in the child:
1// Parent2view! {3 <Card title={"News".to_string()}>4 {#slot:body}5 <p>{"This content goes into the card body."}</p>6 {/slot}7 </Card>8}910// Child component11pub struct Card {12 pub title: String,13 pub body: tidos::Slot,14}1516impl Component for Card {17 fn to_render(&self, page: &mut Page) {18 view! {19 <div class="card">20 <h2>{&self.title}</h2>21 <div class="body">22 @slot{self.body}23 </div>24 </div>25 }26 }27}
Default Properties
Components whose struct derives Default can use .. after the explicit props to fill all remaining fields with their default values. The struct must implement Default, either via #[derive(Default)] or manually:
1use tidos::{view, Component, Page};23#[derive(Default)]4pub struct Button {5 pub label: String,6 pub disabled: bool,7 pub variant: String,8}910impl Component for Button {11 fn to_render(&self, page: &mut Page) {12 view! {13 <button data-variant={&self.variant}>14 {&self.label}15 </button>16 }17 }18}1920// Only set label — the rest use Default::default()21view! { <Button label={"Save".to_string()} .. /> }2223// All fields use Default24view! { <Button .. /> }
The head! Macro
Call head! to inject elements into the page head. The call is deduplicated by a compile-time UUID, so it is safe to call from components rendered in loops:
1use tidos::{head, view, Component, Page};23impl Component for MyComponent {4 fn to_render(&self, page: &mut Page) {5 head! {6 <link rel="stylesheet" href="/fonts.css" />7 <script src="/analytics.js"></script>8 }9 view! {10 <section>{"Content here"}</section>11 }12 }13}
The scoped_css! Macro
scoped_css! reads a CSS file at compile time, generates a unique class name, and injects a scoped style tag into the head. Apply the returned class to your root element:
1use tidos::{scoped_css, view, Component, Page};23impl Component for Card {4 fn to_render(&self, page: &mut Page) {5 view! {6 <div class={scoped_css!("./card.css")}>7 <h2>{&self.title}</h2>8 </div>9 }10 }11}1213// card.css14// & {15// background: #1a1a2e;16// border-radius: 8px;17// padding: 1.5rem;18// }19// & h2 {20// color: #a8dadc;21// }