· 8 min read Posted by Gustavo Fão Valvassori
Styling your components on Compose Web
Styling is an essential part of your app development. With that, you can improve the user experience and make your app look better.
For Web applications, the styling part is mainly done with CSS. CSS stands for Cascading Style Sheets, a language used to describe the presentation of a document. As Compose for Web still uses HTML for rendering the components, we will use CSS as the styling language.
Inline styling
The first way to style your components is by using inline styling. The style is passed as an HTML attribute parameter to the element. For example, if you want to style a div component, you can do it like this:
<div style="background-color: red;">Hello on red background</div>
Compose has a style method inside the attribute builder (which we saw in the last article)
to support this type of customization. This method receives a lambda with the type StyleScope.() -> Unit
. The
StyleScope is a class that has a lot of methods to customize the style of your component. In Compose, the above example
would be like this:
Div(
attrs = {
style { backgroundColor(Color.red) }
}
) {
Text("Hello on red background")
}
Inside the builder scope, you have access to many properties and methods to customize your component. If your styling method is not implemented, you can always use the property method to set a custom property. For example:
Div(
attrs = {
style { property("background-color", Color.red) }
}
) {
Text("Hello on red background")
}
Stylesheets
Inline styling is good for minor customizations on one element. But if you want to reuse them, you will need to use
stylesheets. Stylesheets are a way to define a set of styles that can be applied to multiple components. You can create
an object inheriting from the StyleSheet
class to create a stylesheet with Compose.
object MyCSS: StyleSheet() {
// TODO: Add your styles here
}
Then, to apply this stylesheet to your project, you must declare it on your root component. For that, you should call
the Style()
method with your class inside the renderComposable()
method.
fun main() {
renderComposable(rootElementId = "root") {
Style(MyCSS)
App()
}
}
When you call the Style()
method, it will create a <style>
tag on your HTML document and add all the styles from
your stylesheet to it. If you miss this step, your elements will not be styled when you set a style to them.
CSS DSL
When you create a stylesheet, you can define multiple attributes on it. Each attribute will be implemented using the
by style
delegate method, becoming a CSS class that can be used on your components. The value return by this delegate
is a String value with the name of the class.
object MyCSS: StyleSheet() {
val redBackground by style {
property("background-color", Color.red)
}
}
After creating your style, you can apply it to your component. For example:
@Composable
fun App() {
Div(
attrs = {
classes(MyCSS.redBackground)
}
) {
Text("Hello on red background")
}
}
self
selector. This selector is a reference to the
current CSS class. You can use it to combine with other selectors to apply styles for specific states. We will see
more about selectors later.Element Type selector
In case you need to apply any changes to all HTML elements with a given tag, you use the type()
selector function.
It will return a selector instance with a style
method for customizations. For example, if you want to change all
table elements, you can do it like this:
object MyCSS: StyleSheet() {
init {
type("td") style {
padding(0.px)
}
}
}
This style will be applied to all elements with the td
tag, like the example below:
@Composable
fun App() {
Table {
Tr {
Td { Text("0,0") }
Td { Text("0,1") }
Td { Text("0,2") }
}
Tr {
Td { Text("1,0") }
Td { Text("1,1") }
Td { Text("1,2") }
}
Tr {
Td { Text("2,0") }
Td { Text("2,1") }
Td { Text("2,2") }
}
}
}
Class selector
In CSS, classes are groups of elements with the same style. Its syntax is very similar to the type selector, but instead
of using the type()
function, you use the className()
function. For example, if you want to change a class named
‘redBackground’, you can do it like this:
object MyCSS: StyleSheet() {
init {
className("redBackground") style {
backgroundColor(Color.red)
}
}
}
This style will be applied to all elements that have the ‘redBackground’ class, like the example below:
fun App() {
Div(
attrs = {
classes("redBackground")
}
) {
Text("Hello on red background")
}
}
ID Selector
When you want to customize specific elements, you can use the ID selector. It is similar to the class selector, but
instead of using the className()
function, you use the id()
function. It will query for the element containing the
given ID. For example, if you want to change the style of an element with the ID ‘myButton’, you can do it like this:
object MyCSS: StyleSheet() {
init {
id("myButton") style {
backgroundColor(Color.red)
}
}
}
This style will only be applied to the element with the ID ‘myButton’, like the example below:
@Composable
fun App() {
// This one will have style
Button(attrs = { id("myButton") }) {
Text("Button with ID")
}
// This one will not have style
Button {
Text("Button without ID")
}
}
Inheritance and Sibilings
Inheritance is a significant part of CSS. It allows you to customize a component based on its parent. With it, you can
change how a child, siblings, or adjacent elements are rendered. You can use the child()
, sibling()
, or adjacent()
methods to pass the self
selector as the parent.
object MyCSS: StyleSheet() {
val myTableEntry by style {
child(self, type("button")) style {
backgroundColor(Color.blue)
}
child(self, type("text")) style {
backgroundColor(Color.red)
}
}
}
Using their selector functions, you can also do the same without the classes, IDs, or types. For example, all buttons inside the table but without needing to create a new class:
object MyCSS: StyleSheet() {
init {
child(type("td"), type("button")) style {
backgroundColor(Color.blue)
}
}
}
Combining styles
You can also combine styles by using the ’+’ operator. For example, if you want to apply a style to all elements with the “btn” and “warning” or “error” classes, you can do it like this:
object MyCSS : StyleSheet() {
val btn by style {
fontSize(20.pt)
self + className("warning") style {
backgroundColor(Color.yellow)
}
self + className("error") style {
backgroundColor(Color.red)
}
}
}
You can also combine multiple attributes from your stylesheet object:
object MyCSS : StyleSheet() {
val warning by style {
// Style for warning
}
val error by style {
// Style for error
}
val btn by style {
fontSize(20.pt)
self + warning style {
backgroundColor(Color.yellow)
}
self + error style {
backgroundColor(Color.red)
}
}
}
Pseudo selectors
In CSS, we have a lot of pseudo-selectors that can be used to apply styles to elements in specific states. For example,
you can apply a style to all button
elements that are being hovered by the mouse. To do that, you can use the hover
pseudo selector using the compose syntax presented above:
object MyCSS : StyleSheet() {
val myButton by style {
backgroundColor(Color.red)
self + hover style {
backgroundColor(Color.blue)
}
}
}
Other pseudo selectors that are supported and you can use are: active
, checked
, disabled
, empty
, enabled
,
first-child
, focus
, nth-child
, etc. You can find more information about pseudo selectors on the
MDN Web Docs and the
StyleSheetBuilder
file from the Compose source sets.
Raw CSS Selectors
If you prefer to write selectors like in plain CSS, you can use the selector()
method or just use it as a string.
This is a “raw” way of doing everything we saw above. For some cases, it may be easier (like for types), but for others,
it may be harder to read. For example, the following code is equivalent to all we saw above:
object MyCSS: StyleSheet() {
init {
"td" style {
padding(0.px)
}
".contentTable" style {
backgroundColor(Color.red)
}
"#myButton" style {
backgroundColor(Color.red)
}
"html, body" style {
height(98.percent)
width(100.percent)
overflow("hidden")
}
"btn > warning" style {
backgroundColor(Color.yellow)
}
"btn > error" style {
backgroundColor(Color.red)
}
".myButton:hover" style {
backgroundColor(Color.blue)
}
}
}
Final Thoughts
Styling is a very important part of your application. Compose provides a ton of ways to customize your components. You can choose to use either inline styling or stylesheets. The DSL has a lot of methods to help you write your style.
This article did not mention a few topics, like media queries, animations, variables, etc. In future articles, we will explore more about styling and how to use it to make your app look better.