Primitives

svg and icon

Draw SVG artwork at any size, tint it like currentColor, and use the kit's Lucide icons.

FNsvg
Usage
use crownui::prelude::*;
 
const LOGO: &str = r##"<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="#6366f1"/><path d="M8 12h8" stroke="#ffffff" stroke-width="2"/></svg>"##;
 
fn brand(active: Signal<bool>) -> impl View {
    hstack((
        svg(LOGO).size(32.0).label("CrownOS"),
        icon(LOGO, bind(move |rt| if active.get(rt) { rgb(0x3b82f6) } else { rgb(0x9ca3af) }))
            .size(16.0),
    ))
    .gap(8.0)
    .items_center()
}
Signature
pub fn svg(source: impl Into<String>) -> Element<SvgKind>;
 
pub fn icon(source: impl Into<String>, tint: impl IntoProp<Color>) -> Element<SvgKind>;

svg draws vector artwork from SVG source text. icon draws the same artwork with every color replaced by one tint, the way icons drawn with currentColor behave on the web. The kit adds kit::icon for its built-in Lucide set.

Parameters

sourceimpl Into<String>required

A complete SVG document as text, with its root <svg> element.

tintimpl IntoProp<Color>required

For icon: the color every fill and stroke becomes. A literal, a Signal<Color>, a Memo<Color> or a bind.

Returns

Element<SvgKind>Element<SvgKind>

An element with the Image role, sized to the SVG's natural size unless styled.

Sizing

The element starts at the SVG's own width and height. Any size you give it with .size(), .w() or .h() scales the picture to fit inside, keeping its aspect ratio, and centers it. The source is parsed once when the element mounts.

svg(LOGO)            // 24 x 24, from the SVG
svg(LOGO).size(96.0) // scaled to 96 x 96
svg(LOGO).w(200.0)   // 200 wide, 24 tall, the picture centered at 24 x 24

Tinting

icon replaces the color of every fill and stroke with tint and keeps each shape's own alpha. A half transparent shape stays half transparent in the new color. Because tint is a property, a signal or bind recolors the icon without rebuilding it.

let hovered = cx.signal(false);
 
icon(source, bind(move |rt| if hovered.get(rt) { WHITE } else { rgb(0x9ca3af) }))
    .size(20.0)
    .on_pointer_enter(move |cx, _| hovered.set(cx, true))
    .on_pointer_leave(move |cx, _| hovered.set(cx, false))

Supported SVG

ContentSupport
Paths and basic shapes (rect, circle, ellipse, line, polyline, polygon)Drawn, after usvg turns them into paths.
Groups and transformsDrawn. Transforms are applied to the path points.
Solid fill and stroke colors with opacityDrawn.
Stroke width, line caps and line joinsDrawn. Miter clip joins draw as miter.
Gradients and patternsSkipped. A shape painted only with them is not drawn.
Embedded imagesSkipped.
TextSkipped.

Invalid SVG logs CUI0501 and the element draws nothing.

Kit icons

The kit includes the Lucide icon set as constants in kit::icons, such as icons::TRASH, icons::COPY and icons::SEARCH. Each constant is an SVG body without the outer <svg> element, so it is not valid input for svg or icon. Draw them with the kit functions, which wrap the body in a 24 unit view box with round caps and joins and paint it in a theme color:

pub fn kit::icon(body: &'static str) -> Element<IconKind>;
pub fn kit::icon_muted(body: &'static str) -> Element<IconKind>;
pub fn kit::icon_colored(
    body: &'static str,
    stroke_width: f32,
    color: impl Fn(&Theme) -> Color + 'static,
) -> Element<IconKind>;
FunctionColorStroke
kit::icon(body)theme.text.icon2 units
kit::icon_muted(body)theme.text.icon_muted2 units
kit::icon_colored(body, stroke_width, color)Whatever color picks from the themestroke_width units

Kit icons are 16 logical pixels by default. Resize them with .size(..). They follow theme changes live.

use crownui::kit::{self, icons};
 
hstack((
    kit::icon(icons::TRASH),
    kit::icon_muted(icons::COPY).size(20.0),
    kit::icon_colored(icons::SEARCH, 1.5, |theme| theme.text.icon),
))
.gap(12.0)

Accessibility

Both elements get the Image role but no name. Label meaningful artwork with .label(".."). An icon inside a labelled button needs no label of its own.

Cost

Each svg or icon element parses its source when it mounts and keeps the flattened paths. Each repaint, such as a tint change, copies those paths into a new draw list. Small paths such as icons get analytic edge coverage in Impeller, so they stay sharp at any size. For a toolbar this cost is negligible. For hundreds of icons that change every frame, draw them in one canvas.

See also