Kotlin HTML DSL
Introduction
Domain Specific Languages (DSLs) in Kotlin provide a powerful way to express complex structures in a readable and maintainable way. One particularly useful application is creating HTML documents directly in Kotlin code. The Kotlin HTML DSL allows developers to generate HTML using a type-safe, IDE-friendly syntax that closely resembles the structure of HTML itself, but with all the benefits of Kotlin.
In this tutorial, we'll explore how to use Kotlin HTML DSL to create web pages programmatically. This approach is particularly useful for dynamic content generation, server-side rendering, or when you need to generate HTML from data.
Getting Started with Kotlin HTML DSL
Setting Up Your Project
To get started with Kotlin HTML DSL, you'll need to add the appropriate dependencies to your project. The most popular library for this purpose is kotlinx.html
.
For Gradle (build.gradle.kts):
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.8.0")
}
For Maven:
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-html-jvm</artifactId>
<version>0.8.0</version>
</dependency>
Basic Structure
The basic structure of a Kotlin HTML DSL follows HTML's hierarchical nature. Here's a simple example:
import kotlinx.html.*
import kotlinx.html.stream.appendHTML
fun main() {
val html = StringBuilder().appendHTML().html {
head {
title("My First Kotlin HTML Page")
}
body {
h1 {
+"Hello, Kotlin HTML DSL!"
}
p {
+"This is a paragraph created using Kotlin HTML DSL."
}
}
}.toString()
println(html)
}
Output:
<html>
<head>
<title>My First Kotlin HTML Page</title>
</head>
<body>
<h1>Hello, Kotlin HTML DSL!</h1>
<p>This is a paragraph created using Kotlin HTML DSL.</p>
</body>
</html>
Let's break down what's happening:
- We import the necessary packages from
kotlinx.html
- We create a
StringBuilder
and useappendHTML()
to start building our HTML - We define the structure using nested function calls that mimic HTML structure
- We use the
+
operator to add text content to elements - The result is a properly formatted HTML string
Key Features of Kotlin HTML DSL
Adding Attributes
You can add attributes to HTML elements as named parameters:
import kotlinx.html.*
import kotlinx.html.stream.appendHTML
fun main() {
val html = StringBuilder().appendHTML().html {
head {
title("Attributes Example")
}
body {
div {
id = "container"
classes = setOf("content", "main-section")
p {
style = "color: blue; font-size: 16px;"
+"This paragraph has custom styling."
}
}
a(href = "https://kotlinlang.org") {
target = ATarget.blank
+"Visit Kotlin's website"
}
}
}.toString()
println(html)
}
Output:
<html>
<head>
<title>Attributes Example</title>
</head>
<body>
<div id="container" class="content main-section">
<p style="color: blue; font-size: 16px;">This paragraph has custom styling.</p>
</div>
<a href="https://kotlinlang.org" target="_blank">Visit Kotlin's website</a>
</body>
</html>
Handling Events
You can add event handlers with a similar syntax:
import kotlinx.html.*
import kotlinx.html.js.*
import kotlinx.html.stream.appendHTML
fun main() {
val html = StringBuilder().appendHTML().html {
head {
title("Events Example")
}
body {
button {
type = ButtonType.button
onClick = "alert('Hello from Kotlin!')"
+"Click me"
}
}
}.toString()
println(html)
}
Output:
<html>
<head>
<title>Events Example</title>
</head>
<body>
<button type="button" onclick="alert('Hello from Kotlin!')">Click me</button>
</body>
</html>
Forms and Input Elements
Creating forms is straightforward with the DSL:
import kotlinx.html.*
import kotlinx.html.stream.appendHTML
fun main() {
val html = StringBuilder().appendHTML().html {
head {
title("Form Example")
}
body {
form {
action = "/submit"
method = FormMethod.post
div {
label {
htmlFor = "name"
+"Name:"
}
input {
id = "name"
name = "name"
type = InputType.text
required = true
}
}
div {
label {
htmlFor = "email"
+"Email:"
}
input {
id = "email"
name = "email"
type = InputType.email
required = true
}
}
button {
type = ButtonType.submit
+"Submit"
}
}
}
}.toString()
println(html)
}
Output:
<html>
<head>
<title>Form Example</title>
</head>
<body>
<form action="/submit" method="post">
<div>
<label for="name">Name:</label>
<input id="name" name="name" type="text" required="">
</div>
<div>
<label for="email">Email:</label>
<input id="email" name="email" type="email" required="">
</div>
<button type="submit">Submit</button>
</form>
</body>
</html>
Creating Reusable Components
One of the great advantages of using Kotlin HTML DSL is the ability to create reusable components:
import kotlinx.html.*
import kotlinx.html.stream.appendHTML
// Define a reusable component
fun FlowContent.card(title: String, content: String) {
div {
classes = setOf("card")
div {
classes = setOf("card-header")
h3 { +title }
}
div {
classes = setOf("card-body")
p { +content }
}
}
}
fun main() {
val html = StringBuilder().appendHTML().html {
head {
title("Reusable Components")
style {
+"""
.card {
border: 1px solid #ddd;
border-radius: 8px;
margin: 10px;
overflow: hidden;
}
.card-header {
background-color: #f1f1f1;
padding: 10px;
}
.card-header h3 {
margin: 0;
}
.card-body {
padding: 15px;
}
"""
}
}
body {
h1 { +"My Cards" }
div {
classes = setOf("container")
// Use the reusable component
card("First Card", "This is the content of the first card.")
card("Second Card", "This is the content of the second card.")
card("Third Card", "This is the content of the third card.")
}
}
}.toString()
println(html)
}
Output:
<html>
<head>
<title>Reusable Components</title>
<style>
.card {
border: 1px solid #ddd;
border-radius: 8px;
margin: 10px;
overflow: hidden;
}
.card-header {
background-color: #f1f1f1;
padding: 10px;
}
.card-header h3 {
margin: 0;
}
.card-body {
padding: 15px;
}
</style>
</head>
<body>
<h1>My Cards</h1>
<div class="container">
<div class="card">
<div class="card-header">
<h3>First Card</h3>
</div>
<div class="card-body">
<p>This is the content of the first card.</p>
</div>
</div>
<div class="card">
<div class="card-header">
<h3>Second Card</h3>
</div>
<div class="card-body">
<p>This is the content of the second card.</p>
</div>
</div>
<div class="card">
<div class="card-header">
<h3>Third Card</h3>
</div>
<div class="card-body">
<p>This is the content of the third card.</p>
</div>
</div>
</div>
</body>
</html>
Real-World Example: Dynamic Content Generation
Let's create a more practical example - a dynamic blog post list:
import kotlinx.html.*
import kotlinx.html.stream.appendHTML
import java.time.LocalDate
import java.time.format.DateTimeFormatter
// Data class representing a blog post
data class BlogPost(
val id: Int,
val title: String,
val summary: String,
val author: String,
val date: LocalDate,
val tags: List<String>
)
// Extension function to render a blog post
fun FlowContent.blogPostCard(post: BlogPost) {
article {
classes = setOf("blog-post")
attributes["data-post-id"] = post.id.toString()
h2 {
classes = setOf("post-title")
+post.title
}
div {
classes = setOf("post-meta")
span {
classes = setOf("author")
+"By ${post.author}"
}
span {
classes = setOf("date")
+post.date.format(DateTimeFormatter.ISO_LOCAL_DATE)
}
}
p {
classes = setOf("post-summary")
+post.summary
}
div {
classes = setOf("tags")
post.tags.forEach { tag ->
span {
classes = setOf("tag")
+tag
}
}
}
a(href = "/posts/${post.id}") {
classes = setOf("read-more")
+"Read more"
}
}
}
fun main() {
// Sample blog posts
val blogPosts = listOf(
BlogPost(
1,
"Getting Started with Kotlin",
"Learn the basics of Kotlin programming language...",
"Jane Doe",
LocalDate.of(2023, 5, 15),
listOf("Kotlin", "Programming")
),
BlogPost(
2,
"Understanding Kotlin DSLs",
"Dive deep into Kotlin's DSL capabilities...",
"John Smith",
LocalDate.of(2023, 6, 22),
listOf("Kotlin", "DSL", "Advanced")
),
BlogPost(
3,
"Web Development with Kotlin",
"Exploring modern web development using Kotlin...",
"Alice Johnson",
LocalDate.of(2023, 7, 10),
listOf("Kotlin", "Web", "Development")
)
)
val html = StringBuilder().appendHTML().html {
head {
title("Blog Posts")
style {
+"""
body { font-family: Arial, sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; }
.blog-post { border-bottom: 1px solid #eee; padding: 20px 0; }
.post-title { margin-top: 0; color: #333; }
.post-meta { display: flex; justify-content: space-between; color: #666; margin-bottom: 15px; font-size: 0.9em; }
.post-summary { margin-bottom: 15px; }
.tags { margin: 15px 0; }
.tag { background: #f1f1f1; padding: 3px 8px; border-radius: 3px; margin-right: 5px; font-size: 0.8em; }
.read-more { display: inline-block; color: #0066cc; text-decoration: none; font-weight: bold; }
.read-more:hover { text-decoration: underline; }
"""
}
}
body {
header {
h1 { +"My Tech Blog" }
}
section {
id = "blog-posts"
blogPosts.forEach { post ->
blogPostCard(post)
}
}
footer {
p { +"© 2023 My Tech Blog. All rights reserved." }
}
}
}.toString()
println(html)
}
This generates a complete HTML page with styled blog post cards, using our custom data and component abstractions. The output is too long to include here, but it would render a clean blog post listing page with all the styling specified.
Integration with Web Frameworks
Kotlin HTML DSL can be integrated with various web frameworks:
With Ktor
import io.ktor.application.*
import io.ktor.html.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import kotlinx.html.*
fun main() {
embeddedServer(Netty, port = 8080) {
routing {
get("/") {
call.respondHtml {
head {
title("Ktor with Kotlin HTML DSL")
}
body {
h1 {
+"Welcome to my Ktor website!"
}
p {
+"This page was generated using Kotlin HTML DSL."
}
a(href = "/about") {
+"About Us"
}
}
}
}
get("/about") {
call.respondHtml {
head {
title("About Us")
}
body {
h1 {
+"About Us"
}
p {
+"This is the about page created with Kotlin HTML DSL."
}
a(href = "/") {
+"Back to Home"
}
}
}
}
}
}.start(wait = true)
}
With Spring Boot
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import kotlinx.html.*
import kotlinx.html.stream.appendHTML
import javax.servlet.http.HttpServletResponse
@SpringBootApplication
class KotlinHtmlDslApplication
fun main(args: Array<String>) {
runApplication<KotlinHtmlDslApplication>(*args)
}
@Controller
class HomeController {
@GetMapping("/")
fun home(response: HttpServletResponse) {
response.contentType = "text/html"
response.writer.append(
StringBuilder().appendHTML().html {
head {
title("Spring Boot with Kotlin HTML DSL")
}
body {
h1 {
+"Welcome to Spring Boot!"
}
p {
+"This page is rendered using Kotlin HTML DSL in Spring Boot."
}
}
}.toString()
)
}
}
Summary
Kotlin HTML DSL provides a powerful, type-safe, and concise way to generate HTML directly from Kotlin code. By leveraging Kotlin's language features, it offers several advantages:
- Type safety - Compilation errors will catch mismatched tags or incorrect attributes
- IDE support - Code completion, documentation, and refactoring capabilities
- Reusability - Create custom components and functions for common patterns
- Integration - Easily integrate with frameworks like Ktor or Spring Boot
- Dynamic content - Generate HTML based on data or user inputs programmatically
Whether you're building a server-side rendered web application, generating HTML emails, or creating static site generators, Kotlin HTML DSL provides a clean and maintainable approach to HTML generation.
Additional Resources
Exercises
- Create a simple HTML page that includes a header, navigation menu, main content area, and footer.
- Build a product listing page that displays items from a list of products with images, prices, and "Add to Cart" buttons.
- Create a reusable form component that handles different types of input fields and validation messages.
- Generate an HTML email template with dynamic content based on user information.
- Build a simple blog site with multiple pages using Kotlin HTML DSL integrated with a web framework of your choice.
If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)