日本語

Theming

The components read their colors and radius from design tokens, which a theme sets for the page and a style sets for one element.

Tokens

The token module has one function per token, named as in shadcn/ui: background, foreground, primary, muted, border, ring, radius, and the rest. Each returns a Token, which is a CSS custom property with a typed default. A page that sets nothing gets the light values.

use hypersig::style::value::{Color, rem};
use hypersig::style::{Media, Style, Theme};
use hypersig::{Page, Signals};
use hypersig_ui::{ButtonSize, ButtonVariant, button, token};
use serde::{Deserialize, Serialize};

#[derive(Signals, Serialize, Deserialize)]
struct App {
    saved: bool,
}

// A blue primary color, lighter in the dark theme, and rounder corners.
let brand = Theme::new()
    .set(&token::primary(), Color::hex(0x0025_63eb))
    .set(&token::radius(), rem(1))
    .on(Media::Dark, Theme::new().set(&token::primary(), Color::hex(0x0060_a5fa)));

let html = Page::new("App", App { saved: false })
    .global(hypersig_ui::global())
    .theme(hypersig_ui::theme())
    .theme(brand)
    .child(
        button::<App>(ButtonVariant::Default, ButtonSize::Default)
            // One element can set a token for itself and what is inside it.
            .css(Style::new().token(&token::primary(), Color::hex(0x0016_a34a)))
            .text("Save"),
    )
    .render();
assert!(html.contains("--primary:#2563eb"));

Light and dark

hypersig_ui::theme() sets the dark values under prefers-color-scheme: dark. To let a signal pick the scheme instead, put hypersig_ui::dark() or hypersig_ui::light() on an element with css_when or css_match. Each sets every color token for the element and those inside it, and color-scheme with them, so native controls such as a scrollbar follow.

Result

theme.rs
use hypersig::html::div;
use hypersig::style::Style;
use hypersig::style::pattern::stack;
use hypersig::style::value::rem;
use hypersig::{Page, Signals};
use hypersig_ui::{
    ButtonSize, ButtonVariant, Level, button, card, card_content, card_description, card_header,
    card_title, select_field, switch, token,
};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, Copy, PartialEq)]
pub enum Scheme {
    System,
    Light,
    Dark,
}

#[derive(Signals, Serialize, Deserialize)]
pub struct Preview {
    scheme: Scheme,
    on: bool,
}

pub fn page() -> Page<Preview> {
    let initial = Preview {
        scheme: Scheme::System,
        on: true,
    };
    Page::new(
        "Preview",
        initial,
    )
    .child(
        div()
            .css(
                stack(rem(1))
                    .padding(rem(1))
                    .background_color(token::background().var())
                    .color(token::foreground().var()),
            )
            .css_match(
                Preview::scheme(),
                [
                    (Scheme::Light, hypersig_ui::light()),
                    (Scheme::Dark, hypersig_ui::dark()),
                ],
            )
            .child(
                div()
                    .css(Style::new().max_width(rem(12)))
                    .child(select_field(
                        "Theme",
                        Preview::scheme(),
                        [
                            (
                                Scheme::System,
                                "System",
                            ),
                            (
                                Scheme::Light,
                                "Light",
                            ),
                            (
                                Scheme::Dark,
                                "Dark",
                            ),
                        ],
                    )),
            )
            .child(
                card()
                    .child(
                        card_header()
                            .child(card_title(Level::H2).text("Notifications"))
                            .child(card_description().text("Choose what reaches your inbox.")),
                    )
                    .child(
                        card_content()
                            .css(stack(rem(1)))
                            .child(switch(
                                "Product updates",
                                Preview::on(),
                            ))
                            .child(div().child(
                                button(ButtonVariant::Default, ButtonSize::Sm).text("Save"),
                            )),
                    ),
            ),
    )
}
The tokens are plain values of hypersig::style. Tokens and themes covers how they are written to the page.