Styling
Basics
The main type to style the DOM via css is Style
:
Bonito.Styles
— TypeStyles(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.
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
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
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.