Conditions
Style::on applies part of a style only under a condition. The condition can be a pseudo-class, a media feature, a container width, or the state of a marked ancestor or sibling.
Pseudo-classes, media, and containers
use hypersig::style::value::{Color, Display, Opacity, pct, quoted, rem};
use hypersig::style::{Container, Media, PseudoClass, PseudoElement, Style};
let link = Style::new()
.color(Color::hex(0x0025_63eb))
// A pseudo-class.
.on(PseudoClass::Hover, Style::new().color(Color::hex(0x001d_4ed8)))
// Conditions nest: hovered and focused at once.
.on(
PseudoClass::Hover,
Style::new().on(PseudoClass::FocusVisible, Style::new().opacity(Opacity::Number(0.8))),
)
// A pseudo-element.
.pseudo(PseudoElement::After, Style::new().content(quoted(" →")));
let column = Style::new()
.width(pct(100))
// From 48rem wide, and from 64rem wide.
.on(Media::Md, Style::new().width(pct(50)))
.on(Media::Lg, Style::new().width(pct(33)))
// The user's settings.
.on(Media::ReducedMotion, Style::new().display(Display::Block))
// The nearest query container, from 24rem wide.
.on(Container::Sm, Style::new().padding(rem(2)));PseudoClassandPseudoElementare generated from MDN's data.pseudoputs declarations on a pseudo-element. A pseudo-class written inside it applies to the element itself, sopseudo(Before, Style::new().on(Hover, ..))becomes:hover::before.- The user-action pseudo-classes are written to the stylesheet in a fixed order. As a result,
:activetakes precedence over:hoverwhile a button is pressed, and:disabledtakes precedence over all of them. Mediahas the breakpointsSm,Md,Lg,Xl, andXxl, min-width queries at 40, 48, 64, 80, and 96rem, the widths Tailwind CSS and Panda CSS use. It also hasDark,Light,ReducedMotion,Print,Landscape,Portrait,CanHover,CoarsePointer,ForcedColors, andMoreContrast.Containerhas Panda CSS's container sizes from 16rem to 90rem. An ancestor becomes a query container withcontainer_type, and without one the condition never matches.- Conditions can be nested. A style passed to
oncan useonitself, and the inner declarations then apply when both conditions hold.
Group and peer
Style::group marks an ancestor, and Condition::Group(PseudoClass::Hover) applies a style only while that ancestor is hovered. Style::peer marks an earlier sibling, and Condition::Peer(PseudoClass::Checked) applies a style only while that sibling is checked. Both work like the group and peer of Tailwind CSS and Panda CSS.
use hypersig::html::{a, div, input, label, span};
use hypersig::style::value::{Color, Opacity};
use hypersig::style::{Condition, PseudoClass, Style};
use hypersig::{SafeUrl, Signals};
use serde::{Deserialize, Serialize};
#[derive(Signals, Serialize, Deserialize)]
struct Settings {
agreed: bool,
}
// The arrow shows while the pointer is anywhere on the card.
let card = a::<Settings>()
.href(SafeUrl::new("/docs"))
.css(Style::new().group())
.text("Read the docs")
.child(
span()
.css(
Style::new()
.opacity(Opacity::Number(0.0))
.on(Condition::Group(PseudoClass::Hover), Style::new().opacity(Opacity::Number(1.0))),
)
.text(" →"),
);
// The text turns green while the checkbox before it is checked.
let agree = label::<Settings>()
.child(input().attr("type", "checkbox").css(Style::new().peer()).bind(Settings::agreed()))
.child(
span()
.css(Style::new().on(Condition::Peer(PseudoClass::Checked), Style::new().color(Color::GREEN)))
.text("I agree"),
);Result
hover.rs
use hypersig::html::{a, div, input, label, p, span};
use hypersig::style::pattern::stack;
use hypersig::style::value::{
BorderStyle, Color, FontWeight, Opacity, TextDecorationLine, px, rem,
};
use hypersig::style::{Condition, PseudoClass, Style};
use hypersig::{Page, SafeUrl, Signals};
use hypersig_ui::token;
use serde::{Deserialize, Serialize};
#[derive(Signals, Serialize, Deserialize)]
pub struct Settings {
agreed: bool,
}
pub fn page() -> Page<Settings> {
let card = a()
.href(SafeUrl::new("#"))
.css(
stack(rem(0.25))
.group()
.padding(rem(1))
.border_width(px(1))
.border_style(BorderStyle::Solid)
.border_color(token::border().var())
.border_radius(px(8))
.on(
PseudoClass::Hover,
Style::new().background_color(token::muted().var()),
),
)
.child(
p().css(Style::new().font_weight(FontWeight::Number(600.0)))
.text("Read the docs")
.child(
span()
.css(Style::new().opacity(Opacity::Number(0.0)).on(
Condition::Group(PseudoClass::Hover),
Style::new().opacity(Opacity::Number(1.0)),
))
.text(" →"),
),
)
.child(
p().css(Style::new().color(token::muted_foreground().var()).on(
Condition::Group(PseudoClass::Hover),
Style::new().text_decoration_line(TextDecorationLine::Underline),
))
.text("Hover anywhere on the card."),
);
let agree = label()
.child(
input()
.attr("type", "checkbox")
.css(Style::new().peer())
.bind(Settings::agreed()),
)
.child(
span()
.css(
Style::new().margin_left(rem(0.5)).on(
Condition::Peer(PseudoClass::Checked),
Style::new()
.color(Color::hex(0x0015_803d))
.font_weight(FontWeight::Number(600.0)),
),
)
.text("I agree to the terms"),
);
Page::new(
"Settings",
Settings { agreed: false },
)
.child(
div()
.css(stack(rem(1.25)).max_width(rem(20)))
.child(card)
.child(agree),
)
}