UI components
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:
- Variants and sizes use enum props. For example, pass
variant: ButtonVariant::Destructiveto style a destructive action. - Extra attributes go in
attrs. Build them withattributes!. Each component documents which element receives them. Classes are added to the existing classes without resolving conflicting Tailwind utilities. Use a more specific selector or edit the component to override a default reliably. - Pass content as children. Children can contain text, HTML, and other components.
- Reuse styles through class helpers. For example,
button_variantscan give a link the appearance of a button:
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.