maud-extensions
Small, local superpowers for Maud.
Install
Recommended: install the crate as mx so the macro and component surface reads
well at call sites:
If you want the experimental component system too, enable components on the
same dependency:
[]
= { = "maud-extensions", = "0.6.6", = ["components"] }
Core Story
Write plain html! and emit local CSS and JS where they belong:
use html;
use ;
This is still the intended center of gravity:
- no wrapper component macro
- no hidden CSS/JS injection
- no stringly helper names
- plain Maud remains the main language
Experimental Components
The component system is opt-in behind the components feature.
Preferred authoring pattern:
use Markup;
use ;
What this gives you:
- Bon-backed typed builders
Slot<Markup>andSlot<Vec<Markup>>as the slot declaration path#[mx(default)]for the single default slot- explicit colocated
fn css() -> Markupandfn js() -> Markuphelpers - a normal
impl Renderwhere you place(Self::css())/(Self::js())exactly where they belong - builder
.render()just renders the completed component value
Current constraints:
- use
Slot<T>/Slot<Vec<T>>for slot declarations - reserve
#[mx(default)]for selecting the default slot only - use Rust
DefaultorOption<T>for non-slot defaults - if there are multiple slot fields, mark exactly one
#[mx(default)] - repeated slots use
Slot<Vec<T>>plus#[mx(each = item_name)]
Mental model:
#[derive(Component)]owns fields, slots, and the Bon-backed builder- inherent
css()/js()helpers own component-local assets - the
Renderimpl stays explicit and decides where those helpers are emitted
This keeps the builder story ergonomic while leaving rendering explicit and easy to reason about.
Bundled browser-side building blocks
The current CSS/JS/component story is designed to layer on top of a few small browser-side tools:
- Surreal for DOM ergonomics around
me()/any()style behavior - css-scope-inline for colocated scoped CSS transforms
- Preact Signals for the signals runtime surface the crate builds on
maud-extensions is not trying to replace those pieces; it is trying to make
them feel coherent and component-local from Maud.
To bootstrap the browser-side runtime in a page, prefer mx::Init in <head>:
use html;
use Init;
Available bundles:
Init::all()Init::new().surrealjs().scoped_css().signals().build()surreal_scope_inline!()signals_inline!()surreal_scope_signals_inline!()
If you want a more minimal component style, you can still stop at plain
html! + css! + js!. The component system is intentionally a second layer,
not the only way to use the crate.
Named Helpers
When reuse helps, define local helper functions with Rust identifiers:
use html;
use ;
css!;
js!;
Supported css! forms:
css! { ... }css!(name, { ... })
Supported js! forms:
js! { ... }js!(once, { ... })js!(name, { ... })js!(name, once, { ... })
CSS Helper Macros
Inside css! token mode you can use:
raw!(r#"..."#)media!(prelude, { ... })container!(prelude, { ... })supports!(prelude, { ... })layer!(prelude, { ... })keyframes!(prelude, { ... })- unit helpers:
rem!(...)em!(...)px!(...)pct!(...)vw!(...)vh!(...)ms!(...)s!(...)
Example:
use css;
Limits
css!andjs!are placement-sensitive local emittersjs!(once, ...)relies on adata-mx-js-ranmarker on the parent element- CSS token mode only sees Rust-tokenizable input; use
raw!(...)for arbitrary CSS fragments - JavaScript is validated with SWC before emission
- CSS is checked for lightweight syntax and raw-text safety before emission