Skip to main content

Crate tidos

Crate tidos 

Source
Expand description

§Tidos

Tidos is a Rust server-side rendering (SSR) component framework. Write type-safe HTML components directly in Rust using the view! and page! macros — with full support for loops, conditionals, and pattern matching inside your templates.

§Getting started

[dependencies]
tidos = "0.7.2"

# With Rocket integration:
# tidos = { version = "0.7.2", features = ["rocket"] }

# With Rocket + internationalization support:
# tidos = { version = "0.7.2", features = ["rocket", "i18n"] }

§Core concepts

ItemDescription
view!Renders a fragment of HTML. Returns a String.
page!Wraps a full page. Returns a Page ready to return from a route.
ComponentTrait for reusable components; implement to_render.
PageCollects rendered HTML and <head> elements for a full page response.
scoped_css!Injects a scoped <style> into <head> and returns the generated class name.
head!Injects arbitrary HTML into the page <head>.
i18n::i18n!(feature: i18n) Looks up a Fluent translation key.
i18n::enable_i18n!(feature: i18n) Initialises the translation system in main.rs.

§Defining a component

Implement Component on any struct and use view! inside to_render:

use tidos::{view, Component, Page};

pub struct Card {
    pub title: String,
    pub body: String,
}

impl Component for Card {
    fn to_render(&self, page: &mut Page) -> String {
        view! {
            <div class="card">
                <h2>{&self.title}</h2>
                <p>{&self.body}</p>
            </div>
        }
    }
}

§Building a page

Use page! in a route handler to produce a full Page response. Embed components with JSX-like self-closing tags:

use tidos::{page, Component, Page};

#[get("/")]
pub fn index() -> Page {
    page! {
        <main>
            <Card title={ String::from("News") } body={ String::from("Hello world!") } />
        </main>
    }
}

§Template syntax

§Interpolating Rust expressions

Wrap any Rust expression in { } to interpolate it:

use tidos::view;

let name = "Alice";
let count = 42_usize;

view! {
    <p>Hello {name}, you have {count.to_string()} messages.</p>
}

§{#for} — loops

use tidos::view;

let fruits = vec!["apple", "banana", "cherry"];

view! {
    <ul>
        {#for fruit in fruits}
            <li>{fruit}</li>
        {/for}
    </ul>
}

§{#if} — conditionals

use tidos::view;

let age = 20_u32;
let is_american = false;

view! {
    {#if age >= 18 && !is_american}
        <p>Allowed to drink.</p>
    {:else if age >= 21 && is_american}
        <p>Allowed to drink (US rules).</p>
    {:else}
        <p>Not allowed to drink.</p>
    {/if}
}

§{#match} — pattern matching

use tidos::view;

enum Status { Active, Banned, Guest }
let status = Status::Active;

view! {
    {#match status}
        {:case Status::Active}
            <span class="green">Active</span>
        {:case Status::Banned}
            <span class="red">Banned</span>
        {:case _}
            <span class="gray">Guest</span>
    {/match}
}

§Default trait support

Components that implement Default can use the .. shorthand to fill unspecified props with their default values — mirroring Rust’s struct update syntax:

use tidos::{view, Component, Page};

#[derive(Default)]
pub struct Coordinate {
    pub x: usize,
    pub y: usize,
}

impl Component for Coordinate {
    fn to_render(&self, _page: &mut Page) -> String {
        view! {
            <span>{self.x.to_string()}</span>
            <span>{self.y.to_string()}</span>
        }
    }
}

// Only set x; y defaults to 0
view! {
    <Coordinate x={1} .. />
}

The .. flag is only valid on custom components, not on native HTML tags.

§Scoped CSS

scoped_css! reads a CSS file at compile time, generates a unique class name, and injects a <style> block into the page <head>. The macro returns the class name as a &'static str; apply it to the component’s root element. Page::add_elements_to_head deduplicates by UUID, so calling scoped_css! inside a loop is safe.

use tidos::{scoped_css, view, Component, Page};

pub struct Card {
    pub title: String,
}

impl Component for Card {
    fn to_render(&self, page: &mut Page) -> String {
        view! {
            <div class={scoped_css!("./card.css")}>
                <h2>{&self.title}</h2>
            </div>
        }
    }
}

Use CSS nesting so all styles live in one file:

& {
    background: #16213e;
    border-radius: 8px;
}

& h2 {
    color: #a8dadc;
}

§Injecting <head> elements

Use head! inside to_render to add arbitrary HTML to the page <head> (e.g. a <title> or a <link rel="stylesheet">):

use tidos::{head, Component, Page};

pub struct Title {
    pub title: String,
}

impl Component for Title {
    fn to_render(&self, page: &mut Page) -> String {
        head! {
            <title>{&self.title}</title>
        }
        String::new()
    }
}

§Internationalization

See the i18n module for full details. Enable the feature flag and call i18n::enable_i18n! once in main.rs:

tidos = { version = "0.7.2", features = ["rocket", "i18n"] }
use tidos::i18n::{enable_i18n, i18n, Lang};
use tidos::{view, page, Component, Page};

enable_i18n!();

pub struct Greeting;

impl Component for Greeting {
    fn to_render(&self, page: &mut Page) -> String {
        view! {
            <h1>{i18n!("greeting")}</h1>
        }
    }
}

#[get("/<lang>")]
pub fn index(lang: Lang) -> Page {
    page! { <Greeting /> }
}

Modules§

i18n
Internationalization support for Tidos, backed by Fluent.

Macros§

enable_i18n
Initialises the Tidos i18n translation system.
head
Injects HTML into the <head> of the current page.
page
Renders an HTML template and wraps it in a Page.
scoped_css
Injects a scoped CSS file into the page <head> and returns the generated class name.
view
Renders an HTML template to a String.

Structs§

Page
A fully rendered page ready to be returned from a route handler.

Traits§

Component
A reusable UI component.

Attribute Macros§

native_element
Derives Component for a struct that wraps a native Custom Element.