Browse documentation
Documentation

UI components

Edit this guide on GitHub

Hearth UI provides components you can copy into your project and edit. Run hearth ui add to install a component, then change its source to suit your app.

Components use #[component], view!, and Tailwind classes. Their colors and other shared styles come from a theme stylesheet installed by hearth ui init.

Setup

Set up the hearth CLI and the Tailwind integration, then enable the ui feature. This example also uses Fontsource to load the theme's font:

[dependencies]
hearth = { version = "0.1.0", features = ["font-fontsource", "tailwind", "ui"] }

[build-dependencies]
hearth = { version = "0.1.0", default-features = false, features = ["tailwind"] }

Initialize the package

From the package directory, run:

hearth ui init

The command creates components.toml to track installed components and writes the theme to styles.css. Check both files into version control. Run init before using the other UI commands.

If several themes are available, the command asks you to choose one. Pass --theme <name> to select it directly.

Components install into src/components by default. Use --components-dir to choose another directory. In a workspace, use --package <name> with any hearth ui subcommand to select the package.

Wire the theme into Tailwind

Use the installed styles.css as your Tailwind input. It imports Tailwind, defines the theme, and tells Tailwind to scan your Rust source for classes. Configure build.rs to use it:

fn main() {
    hearth::tailwind::BuildConfig::new()
        .input("styles.css")
        .render()
        .unwrap();
}

Load the stylesheet and the font

Load the generated stylesheet in your root layout. If the theme names a web font, load that font too. For example, use the Fontsource integration to load Geist:

use hearth::{
    Result,
    font::fontsource::fontsource_font,
    router::{Slot, layout},
    tailwind,
    view::{View, view},
};

#[layout]
async fn layout(slot: Slot<'_>) -> Result<impl View> {
    Ok(view! {
        <!DOCTYPE html>
        <html>
            <head>
                hearth::font::link(font: fontsource_font!(GEIST))
                <link rel="stylesheet" href=(tailwind::stylesheet!())>
            </head>
            <body>
                (slot)
            </body>
        </html>
    })
}

The theme applies its base styles to <body> when the stylesheet loads.

Adding components

Add components by name:

hearth ui add button

The command copies the source into your components directory, adds its module declaration, and records the installation in components.toml. It also installs any components the requested component depends on.

Module declarations go in src/components.rs, or in src/components/mod.rs if that file already exists. If both files exist, resolve the conflict before adding components.

To install every component the registry offers, pass --all:

hearth ui add --all

Components that are already installed are skipped and reported. Add --overwrite to replace them with the registry's source as well.

To see what is on offer, run:

hearth ui list

The list shows available components and their install status. Use --installed to show only installed components.

Using components

Installed components are ordinary modules in your crate. Import them and use them like any other component:

mod components;

use components::button::button;
use components::card::{
    card, card_content, card_description, card_footer, card_header, card_title,
};
use components::field::{field, field_description, field_group, field_label};
use components::input::input;
use hearth::{Result, view::{View, attributes, component, view}};

#[component]
async fn sign_in() -> Result<impl View> {
    Ok(view! {
        card(
            card_header(
                card_title("Sign in")
                card_description("Use your work email to continue.")
            )
            card_content(
                <form id="sign-in" method="post" action="/login">
                    field_group(
                        field(
                            field_label(attrs: attributes! { for="email" }, "Email")
                            input(
                                attrs: attributes! {
                                    id="email"
                                    name="email"
                                    type="email"
                                    placeholder="you@example.com"
                                    aria-describedby="email-description"
                                }
                            )
                            field_description(
                                attrs: attributes! { id="email-description" },
                                "Use the email address associated with your account."
                            )
                        )
                    )
                </form>
            )
            card_footer(
                button(
                    attrs: attributes! { class="w-full" type="submit" form="sign-in" },
                    "Sign in"
                )
            )
        )
    })
}

The components share a few conventions:

  view! {
      <a href="/login" class=(button_variants(ButtonVariant::Outline, ButtonSize::Md))>
          "Sign in"
      </a>
  }

Read each component's source for its props and behavior. The ui example provides a runnable showcase.

Theming

A theme defines shared styles with CSS variables in styles.css. Components use classes such as bg-primary and text-muted-foreground to read those variables. Edit the values to change the theme across your app.

For example, --background sets the page color, while --card and --card-foreground set card colors. These values can be adjusted independently.

Add the dark class to <html> to enable dark mode for the page, or to another ancestor to enable it for a subtree. Edit the .dark variables in styles.css to change its colors.

The UI commands preserve styles.css after initialization, so you can customize it freely.

Updating components

hearth ui list reports an update when a component's registry source has changed since installation. It compares the current registry hash with the hash saved in components.toml. Edits to your local copy do not affect this comparison.

To pull the newer source, re-add the component:

hearth ui add button --overwrite

This replaces your file with the registry's source. Save any local changes first, then review the diff and reapply the changes you want to keep.

To refresh every component at once, run hearth ui add --all --overwrite. This also installs any component you do not have yet.

Removing components

Remove a component by name:

hearth ui remove button

This deletes the component's file, removes its module declaration, and drops it from components.toml. It does not remove components that were installed as its dependencies.

Custom registries

A custom registry is a Cargo crate that provides component sources and a registry.toml manifest. Point to the manifest's directory in the crate's metadata:

# Cargo.toml of the registry crate
[package.metadata.hearth-ui]
registry = "registry"
# registry/registry.toml
version = 1

[themes.acme]
source = "themes/acme.css"

[components.button]
source = "src/components/button.rs"

[components.data_table]
source = "src/components/data_table.rs"
# Installed alongside data_table. An entry names a component in the same
# registry, or one in another registry by that registry's crate name.
dependencies = ["button", { registry = "other-registry-crate", name = "spinner" }]

Component versions are computed from their source. To distribute an update, publish a new version of the registry crate with the changed source.

To consume a registry, declare its crate as a direct dependency in Cargo.toml. Its components then show up in hearth ui list and can be added with:

hearth ui add data_table --registry my-registry-crate

Without --registry, the command prefers the built-in hearth registry. It asks for confirmation if the component is available only from another registry. All registries share the same install directory. If a component would replace a file from another registry, the command asks before replacing it.

The built-in registry is available through Hearth's ui feature and needs no separate dependency.