Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Html

Html elements are the building blocks of web pages. While other rust frameworks aim for a JSX-like syntax, this library uses a more traditional approach. The goal is to provide a simple and efficient way to create HTML elements without the need for complex syntax, we use the idomatic rust builder pattern.

Natrix uses a single HtmlElement struct to represent all HTML elements. But exposes helper functions for each tag. These are found along side the HtmlElement struct in the html_elements module. Which will most commonly be used via the e alias in the prelude module.

extern crate natrix;
use natrix::prelude::*;
let _: e::HtmlElement<(), _> =
e::div()
;

If you need to construct a element with a tag not found in the library you can use HtmlElement::new.

extern crate natrix;
use natrix::prelude::*;
let _: e::HtmlElement<(), ()> =
e::HtmlElement::new("custom_tag")
;

Children

Children are added using the .child method. This method takes a single child element and adds it to the parent element.

extern crate natrix;
use natrix::prelude::*;
let _: e::HtmlElement<(), _> =
e::div()
    .child(e::button())
    .child(e::h1().child("Hello World!"))
;

tip

the .text method is a alias for .child

Child elements can be any type that implements the Element trait, including other HtmlElement instances, and stdlib types like String, &str, i32, as well as containers such as Option and Result.

Child elements can also be reactive as closures implement the Element trait.

extern crate natrix;
use natrix::prelude::*;

#[derive(Component)]
struct MyComponent {
    pub is_active: bool,
}

impl Component for MyComponent {
    fn render() -> impl Element<Self> {
e::div()
    .child(e::button()
        .text("Click me!")
        .on::<events::Click>(|ctx: E<Self>, _, _| {
            *ctx.is_active = !*ctx.is_active;
        })
    )
    .child(|ctx: R<Self>| {
        if *ctx.is_active {
            Some(e::p().text("Active!"))
        } else {
            None
        }
    })
    }
}

format_elements

You can use the format_elements macro to get format! like ergonomics for elements.

extern crate natrix;
use natrix::prelude::*;

#[derive(Component)]
struct MyComponent {
    pub counter: u8,
    pub target: u8,
}

impl Component for MyComponent {
    fn render() -> impl Element<Self> {
e::h1().children(
    natrix::format_elements!(|ctx: R<Self>| "Counter is {}, just {} clicks left!", *ctx.counter, *ctx.target - *ctx.counter)
)
    }
}

Which expands to effectively:

extern crate natrix;
use natrix::prelude::*;

#[derive(Component)]
struct MyComponent {
    pub counter: u8,
    pub target: u8,
}

impl Component for MyComponent {
    fn render() -> impl Element<Self> {
e::h1()
    .text("Counter is ")
    .child(|ctx: R<Self>| *ctx.counter)
    .text(", just ")
    .child(|ctx: R<Self>| *ctx.target - *ctx.counter)
    .text(" clicks left!")
  }
}

I.e this is much more performant than format! for multiple reasons:

  • You avoid the format machinery overhead.
  • You get fine-grained reactivty for specific parts of the text.

Attributes

Attributes are set using the .attr method. This method takes a key and a value, and sets the attribute on the element.

extern crate natrix;
use natrix::prelude::*;
let _: e::HtmlElement<(), _> =
e::div()
    .attr("data-foo", "bar")
    .attr("data-baz", "qux")
;

Most standard html attributes have type-safe helper functions, for example id, class, href, src, etc. For non-global attributes natrix only exposes them on the supporting elements.

extern crate natrix;
use natrix::prelude::*;
use natrix::dom::attributes;

let _: e::HtmlElement<(), _> =
e::a()
    .href("https://example.com")
    .target(attributes::Target::NewTab) // _blank
    .rel(vec![attributes::Rel::NoOpener, attributes::Rel::NoReferrer])
;

But the following wont compile:

extern crate natrix;
use natrix::prelude::*;
let _: e::HtmlElement<(), _> =
e::div()
    .target("_blank") // error: no method named `target` found for struct `HtmlElement<_, _div>`
;

Attributes can be set by anything that implements the ToAttribute trait, this includes numberics, Option, and bool, and others. Attributes can also be reactive as closures implement the ToAttribute trait.

extern crate natrix;
use natrix::prelude::*;

#[derive(Component)]
struct MyComponent {
    pub is_active: bool,
}

impl Component for MyComponent {
    fn render() -> impl Element<Self> {
e::button()
    .disabled(|ctx: R<Self>| !*ctx.is_active)
    .text("Click me!")
    .on::<events::Click>(|ctx: E<Self>, _, _| {
        *ctx.is_active = !*ctx.is_active;
    })
    }
}

Importantly for the attribute helpers AttributeKind determines what kind of values are allowed for that helper. Important a attribute kind of for example bool also supports Option<bool>, a closure returning bool, etc. For example this wont compile:

extern crate natrix;
use natrix::prelude::*;
let _: e::HtmlElement<(), _> =
e::a()
    .target("_blank") // error: expected `attributes::Target`, found `&'static str`
;

Classes

The .class method is not a alias for .attr, it will add the class to the element, and not replace it. This is because the class attribute is a special case in HTML, and is used to apply CSS styles to elements. The .class method will add the class to the element, and not replace any existing ones.

extern crate natrix;
use natrix::prelude::*;

const FOO: Class = natrix::class!(); // unique class name
const BAR: Class = natrix::class!();

let _: e::HtmlElement<(), _> =
e::div()
    .class(FOO)
    .class(BAR)
;

Classes can also be reactive as closures implement the ToClass trait.

extern crate natrix;
use natrix::prelude::*;

const ACTIVE: Class = natrix::class!();

#[derive(Component)]
struct MyComponent {
    pub is_active: bool,
}

impl Component for MyComponent {
    fn render() -> impl Element<Self> {
e::div()
    .class(|ctx: R<Self>| {
        if *ctx.is_active {
            Some(ACTIVE)
        } else {
            None
        }
    })
    .child(e::button()
        .text("Click me!")
        .on::<events::Click>(|ctx: E<Self>, _, _| {
            *ctx.is_active = !*ctx.is_active;
        })
    )
}
}