13 minutes read

You are already familiar with the Kotlin HTML DSL and know how to send simple html pages to the user. So far we haven't talked about the design of our pages. It's time to improve that. In this topic, we will meet the CSS styles in Ktor and learn how to make our pages look more beautiful.

You are already familiar with the CSS, so we are not going to talk about the available selectors, properties, and their meanings. We will focus specifically on applying the CSS in Ktor with brief examples.

Getting started with CSS DSL

Ktor has a special language called CSS DSL for working with styles. It extends HTML DSL and allows you to specify styles directly in your application code, just as we did with HTML.

We assume that you have already added the HTML DSL artifact to your build.gradle.kts.

To make CSS DSL available in your project, you need to include a dependency in build.gradle.kts:

implementation("org.jetbrains.kotlin-wrappers:kotlin-css:1.0.0-pre.324-kotlin-1.6.10")

Now we have to define the call.respondCss function, which we will use to return the CSS code to the user.

Add the following code to the beginning of the Routing file:

import kotlinx.css.*

suspend inline fun ApplicationCall.respondCss(builder: CssBuilder.() -> Unit) {
    this.respondText(CssBuilder().apply(builder).toString(), ContentType.Text.CSS)
}

fun Application.configureRouting() {
...

The respondCss function receives a CSS DSL construct as an input. Using the CssBuilder().apply(builder).toString() method, we convert the CSS DSL to a plain CSS code, which is just a text. Then we use the respondText function to send that CSS code to the user. With a second parameter, we set the header Content-Type: text/css. This header tells the browser that it's not just the text but the CSS. code.

As you can see, unlike the call.respondHtml, we have to define the call.respondCss function manually. You don't have to memorize its declaration, just copy it along with the import and use it in the further code. The main thing you should understand is that the respondCss function simply converts the CSS DSL constructions into a plain text and sends it to the user with the header Content-Type: text/css.

Great! Now we can start working with the CSS DSL.

Send CSS in response

You know that in order to apply styles to HTML elements; you need to refer to them somehow. For this purpose, the CSS uses selectors that specify to which elements the style should be applied. The most convenient selector is the class selector, which applies your styles to all elements that have the appropriate class attribute. You already know from the HTML DSL topic how to set the class attribute to an HTML element.

Let's write a style.css handler that will return a CSS rule for our h1 element with the "Header" class:

get("/style.css") {
    call.respondCss {
        rule(".Header") {
            color = Color("#0000FF")
        }
    }
}

To set the selector, we use the rule() function, which takes the CSS selector itself as an input. In the curly brackets block after this function, we specify which parameters we want to apply to our selector.

The respondCss function converts all this code into a regular CSS and returns it to the user. When you access the localhost:8080/style.css, you will get:

.Header {
    color: blue;
}

We would get exactly the same result if we created a style.css file with a CSS code and stored it as a static file at localhost:8080/style.css. We can connect this style to our HTML page by simply adding a link to /style.css.

It doesn't matter to the browser whether the style is stored in a static file or is generated by the Ktor handler using the CSS DSL.

get("/html") {
    call.respondHtml(HttpStatusCode.OK) {
        head {
            link(rel = "stylesheet", href = "/style.css")
        }
        body {
            h1(classes = "Header") {
                +"Hello, user"
            }
        }
    }
}

We connect the CSS to the HTML code with the function link(rel = "stylesheet", href = "/style.css"). This command generates the appropriate <link>tag that shows the browser from where the styles for the page should be loaded.

Also, we created an h1 element with the class attribute "Header" to which our styles will be applied.

If you need to specify several classes for an element, you can, as in standard HTML, put them separated by a space: classes = "Header1 Header2 Header3"

Great! Now let's go into the browser to check the result.

html page with a blue text

HTML:

<html>
    <head>
        <link href="/style.css" rel="stylesheet">
    </head>
    <body>
        <h1 class="Header">Hello, user</h1>
    </body>
</html>

As you can see, the styles applied.

Using the id selector

Besides the class selector, we can use the id selector. The styles specified in this selector apply only to the element with a specific ID.

To use this selector, we must specify the id attribute of the element we want to style.

get("/html") {
    call.respondHtml(HttpStatusCode.OK) {
        head {
            link(rel = "stylesheet", href = "/style.css")
        }
        body {
            h1() {
                attributes["id"] = "SpecificHeader"
                +"Hello, user"
            }
        }
    }
}

Also in styles, the id selector starts with "#" instead of "." as in the class selector.

get("/style.css") {
    call.respondCss {
        rule("#SpecificHeader") {
            color = Color("#0000FF")
        }
    }
}

Result:

html page with blue "Hello, user" text

HTML:

<html>
    <head>
        <link href="/style.css" rel="stylesheet">
    </head>
    <body>
        <h1 id="SpecificHeader">Hello, user</h1>
    </body>
</html>

Setting several properties

In addition to the color, let's give our Header a size of 50px and make it italic:

get("/style.css") {
    call.respondCss {
        rule(".Header") {
            color = Color("#0000FF")
            fontSize = LinearDimension("50px")
            fontStyle = FontStyle("italic")
        }
    }
}

Result:

html page with blue itallic text

As you can see, to add several properties to the selector, you just need to specify them in a row in the rule function.

Setting several selectors

Let's add another header to our HTML page.

call.respondHtml(HttpStatusCode.OK) {
    head {
        link(rel = "stylesheet", href = "/style.css")
    }
    body {
        h1(classes = "Header") {
            +"Hello, user"
        }
        h2(classes = "Header2") {
            +"We are glad to see you"
        }
    }
}

We set the "Header2" class for our h2 element to refer to it in our styles.

Now let's make Header2 underlined and set its margin: 60px

call.respondCss {
    rule(".Header") {
        color = Color("#0000FF")
        fontSize = LinearDimension("50px")
        fontStyle = FontStyle("italic")
    }
    rule(".Header2") {
        margin = "60px"
        textDecoration = TextDecoration(setOf(TextDecorationLine.underline))
    }
}

Result:

html page with blue itallic text and underlined text

So, in general form, setting styles in CSS DSL looks like this:

call.respondCss {
    rule("selector1") {
        stylePropetry1 = value1
        stylePropetry2 = value2
        ...
        stylePropetryN = valueN
    }
    rule("selector2") {
        stylePropetry1 = value1
        stylePropetry2 = value2
        ...
        stylePropetryN = valueN
    }
    ...
    rule("selectorN") {
        stylePropetry1 = value1
        stylePropetry2 = value2
        ...
        stylePropetryN = valueN
    }
}

Conclusion

In this topic, we learned about CSS DSL.

We've learned:

  • How to construct the CSS directly in the routing and refer to it from the HTML code.

  • How to use selector by the class and by the ID.

  • How to add several properties for one selector.

  • How to specify several selectors for your style.css.

As you can see, all the familiar features from the CSS world are still available here. So it will be very easy for you to adapt the CSS DSL to your own needs. Now let's put what we've learned into practice.

How did you like the theory?
Report a typo