Styling

Basics

The main type to style the DOM via css is Style:

Bonito.StylesType
Styles(css::CSS...)

Creates a Styles object, which represents a Set of CSS objects. You can insert the Styles object into a DOM node, and it will be rendered as a <style> node. If you assign it directly to DOM.div(style=Style(...)), the styling will be applied to the specific div. Note, that per Session, each unique css object in all Styles across the session will only be rendered once. This makes it easy to create Styling inside of components, while not worrying about creating lots of Style nodes on the page. There are a two more convenience constructors to make Styles a bit easier to use:

Styles(pairs::Pair...) = Styles(CSS(pairs...))
Styles(priority::Styles, defaults...) = merge(Styles(defaults...), priority)

For styling components, it's recommended, to always allow user to merge in customizations of a Style, like this:

function MyComponent(; style=Styles())
    return DOM.div(style=Styles(style, "color" => "red"))
end

All Bonito components are stylable this way.

Info

Why not Hyperscript.Style? While the scoped styling via Hyperscript.Style is great, it makes it harder to create stylable components, since it doesn't allow the deduplication of CSS objects across the session. It's also significantly slower, since it's not as specialized on the deduplication and the camelcase keyword to css attribute conversion is pretty costly. That's also why CSS uses pairs of strings instead of keyword arguments.

Using CSS and pseudo classes

The CSS object allows to specify a selector, which will be used to apply the styling to a specific DOM node. Since the main usage is to apply the Style object to a DOM node, the selector is usually empty and we use it mainly for pseudo classes like :hover:

App() do session
    return DOM.div(
        "This turns red on hover",
        style=Styles(
            CSS(":hover", "color" => "red", "text-size" => "2rem")
        )
    )
end
This turns red on hover

A more involved example is the style we use for Button:

App() do
    style = Styles(
        CSS(
            "font-weight" => 600,
            "border-width" => "1px",
            "border-color" => "#9CA3AF",
            "border-radius" => "0.25rem",
            "padding-left" => "0.75rem",
            "padding-right" => "0.75rem",
            "padding-top" => "0.25rem",
            "padding-bottom" => "0.25rem",
            "margin" => "0.25rem",
            "cursor" => "pointer",
            "min-width" => "8rem",
            "font-size" => "1rem",
            "background-color" => "white",
            "box-shadow" => "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.1) 0px 1px 2px -1px";
        ),
        CSS(
            ":hover",
            "background-color" => "#F9FAFB",
            "box-shadow" => "rgba(0, 0, 0, 0) 0px 0px 0px 0px",
        ),
        CSS(
            ":focus",
            "outline" => "1px solid transparent",
            "outline-offset" => "1px",
            "box-shadow" => "rgba(66, 153, 225, 0.5) 0px 0px 0px 1px",
        ),
    )
    return DOM.div("Hello", style=style)
end
Hello

If we merged a complex Style like the above with a user given Styles object, it will merge all CSS objects with the same selector, allowing to easily overwrite all styling attributes.

This is how one can style a Button:

App() do
    style = Styles(
        CSS("font-weight" => "500"),
        CSS(":hover", "background-color" => "silver"),
        CSS(":focus", "box-shadow" => "rgba(0, 0, 0, 0.5) 0px 0px 5px"),
    )
    button = Button("Click me"; style=style)
    return button
end

Using Styles as global Stylesheet

One can also define a global stylesheet with Styles using selectors to style parts of an HTML document. This can be handy to set some global styling, but please be careful, since this will affect the whole document. That's also why we need to set a specific attribute selector for all, to not affect the whole documentation page. This will not happen when assigning a style to DOM.div(style=Styles(...)), which will always just apply to that particular div and any other div assigned to. Note, that a style object directly inserted into the DOM will be rendered exactly where it occurs without deduplication!

App() do
    style = Styles(
        CSS("*[our-style]", "font-style" => "italic"),
        CSS("p[our-style]", "color" => "red"),
        CSS(".myClass[our-style]", "text-decoration" => "underline"),
        CSS("#myId[our-style]", "font-family" => "monospace"),
        CSS("p.myClass#myId[our-style]", "font-size" => "1.5rem")
    )
    return DOM.div(
        style,
        DOM.div(ourStyle=1,
            DOM.p(class="myClass", id="myId", "I match everything.", ourStyle=1),
            DOM.p("I match the universal and type selectors only.", ourStyle=1)
        )
    )
end

I match everything.

I match the universal and type selectors only.