Utilities & Testing
Responsive design, CSS-in-Kotlin, file handling, and component testing.
Responsive design
The ResponsiveController tracks the browser window size and exposes the current display type as a
reactive Stream. Access it from any component:
class MyComponent(ctx: Ctx<Props>) : Component<Props>(ctx) {
// Subscribe to the responsive controller — redraws when the breakpoint changes
private val responsive by subscribingTo(responsiveCtrl)
override fun VDom.render() {
when (responsive.displayType) {
DisplayType.Mobile -> renderMobileLayout()
DisplayType.Tablet -> renderTabletLayout()
DisplayType.Desktop -> renderDesktopLayout()
}
}
} Custom breakpoints
By default, Kraft registers a ResponsiveController with standard breakpoints.
You can override it in the app builder:
val app = kraftApp {
responsive(
ResponsiveController(
breakpoints = ResponsiveController.Breakpoints(
tablet = 600, // default: 768
desktop = 1024, // default: 1200
)
)
)
routing { /* ... */ }
} Default breakpoints
If you don't configure anything, the defaults are:
| Display type | Window width |
|---|---|
Mobile | < 768px |
Tablet | 768px – 1199px |
Desktop | ≥ 1200px |
Convenience checks on the state:
responsive.isDesktop // true when >= 1200px
responsive.isMobile // true when < 768px
responsive.isNotDesktop // tablet or mobile
responsive.isNotMobile // tablet or desktop
responsive.windowSize // Vector2D with current width and height CSS & Styling
Kraft supports CSS-in-Kotlin through the StyleSheet class. Define rules as delegated properties —
class names are automatically mangled to avoid collisions:
object MyStyles : StyleSheet() {
val container by rule {
display = Display.flex
justifyContent = JustifyContent.center
padding = Padding(16.px)
}
val highlight by rule {
backgroundColor = Color("#fff3cd")
borderRadius = BorderRadius(4.px)
}
} All three stylesheet types (StyleSheet, RawStyleSheet, StyleSheetTag)
auto-mount by default — they inject their CSS into the page as soon as the object is initialized. No manual
StyleSheets.mount() call needed.
To opt out, pass autoMount = false and mount manually when needed:
object LazyStyles : StyleSheet(autoMount = false) {
val card by rule { padding = Padding(12.px) }
}
// Mount later
StyleSheets.mount(LazyStyles)
// Unmount to remove
StyleSheets.unmount(LazyStyles) Use the generated class names in your components. Each rule gets a mangled class name to avoid collisions:
override fun VDom.render() {
div(MyStyles.container.name) {
div(MyStyles.highlight.name) {
+"Highlighted content"
}
}
} Nested rules
Target child elements within a rule using nested CSS selectors:
object CardStyles : StyleSheet() {
val card by rule {
padding = Padding(12.px)
// nested: targets h2 elements inside .card
"h2" {
fontSize = FontSize(1.2.em)
fontWeight = FontWeight.bold
}
// nested: targets > .content children
"> .content" {
marginTop = 8.px
}
}
} Scoped rules
Create rules scoped to a parent rule using the rule(contextRule) overload:
val card by rule {
padding = Padding(12.px)
}
// generates: .card_abc123.cardTitle_def456
val cardTitle by rule(card) {
fontSize = FontSize(1.2.em)
fontWeight = FontWeight.bold
} Raw CSS and external stylesheets
For raw CSS strings or external stylesheet links — both auto-mount by default:
// Inject a raw CSS string (auto-mounts immediately)
val rawCss = RawStyleSheet("""
.custom-class { color: red; }
""")
// Link an external stylesheet (auto-mounts immediately)
val externalCss = StyleSheetTag {
href = "https://cdn.example.com/styles.css"
} File handling
Read files from a file input as Base64-encoded data:
input {
type = InputType.file
multiple = true
onChange { evt ->
launch {
val files = evt.target.unsafeCast<HTMLInputElement>().files!!
val loaded: List<LoadedFileBase64> = files.loadAllAsBase64()
loaded.forEach { file ->
println(file.file.name) // original filename
println(file.mimeType) // e.g. "image/png"
println(file.dataBase64) // base64 content
println(file.dataUrl) // full data URL
}
}
}
} Responsive images
The SrcSetImage component generates srcset and sizes attributes
automatically from a single image URL:
SrcSetImage(
src = "https://cdn.example.com/photo.jpg",
sizes = ImageSizes.default,
alt = "A photo",
)