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.2", = ["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.
The macro that turns the impl-local component surface on is:
That impl macro is what makes render!, css!, and js! part of the
component render pipeline.
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#[mx::component] implblocks with colocated:render! { ... }css! { ... }js!(once, { ... })
- builder
.render()automatically includes impl-local CSS and JS in the rendered root - no manual
impl Renderglue just to stitch component-local CSS/JS into the output
Current constraints:
- use
Slot<T>/Slot<Vec<T>>for slot declarations - if there are multiple slot fields, mark exactly one
#[mx(default)] - repeated slots use
Slot<Vec<T>>plus#[mx(each = item_name)] #[mx::component]impls currently allow:- exactly one
render! - at most one
css! - at most one
js!
- exactly one
Mental model:
#[derive(Component)]owns fields, slots, and the Bon-backed builder#[mx::component]owns the render root and colocated CSS/JS blocks- builder
.render()goes through the render hook produced by the impl macro
This keeps the builder and the impl-local render/assets story explicit without
requiring manual (Self::css()) / (Self::js()) emission in the render body.
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.
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