Declarative framework for sharing UIs across multiple platforms with Kotlin, including Gradle plugin, component libraries, and web support
—
Compose for Web provides a complete web development framework with type-safe DOM manipulation, CSS styling, SVG support, and testing utilities. It enables building modern web applications using Compose's declarative paradigm while maintaining direct access to web platform APIs.
Type-safe HTML element creation with full attribute and event support.
/**
* Structural HTML elements
*/
@Composable fun Div(
attrs: AttrBuilderContext<HTMLDivElement>? = null,
content: ContentBuilder<HTMLDivElement>? = null
)
@Composable fun Header(
attrs: AttrBuilderContext<HTMLElement>? = null,
content: ContentBuilder<HTMLElement>? = null
)
@Composable fun Main(
attrs: AttrBuilderContext<HTMLElement>? = null,
content: ContentBuilder<HTMLElement>? = null
)
@Composable fun Section(
attrs: AttrBuilderContext<HTMLElement>? = null,
content: ContentBuilder<HTMLElement>? = null
)
@Composable fun Article(
attrs: AttrBuilderContext<HTMLElement>? = null,
content: ContentBuilder<HTMLElement>? = null
)
@Composable fun Nav(
attrs: AttrBuilderContext<HTMLElement>? = null,
content: ContentBuilder<HTMLElement>? = null
)
@Composable fun Footer(
attrs: AttrBuilderContext<HTMLElement>? = null,
content: ContentBuilder<HTMLElement>? = null
)
/**
* Text content elements
*/
@Composable fun H1(
attrs: AttrBuilderContext<HTMLHeadingElement>? = null,
content: ContentBuilder<HTMLHeadingElement>? = null
)
@Composable fun H2(
attrs: AttrBuilderContext<HTMLHeadingElement>? = null,
content: ContentBuilder<HTMLHeadingElement>? = null
)
@Composable fun H3(
attrs: AttrBuilderContext<HTMLHeadingElement>? = null,
content: ContentBuilder<HTMLHeadingElement>? = null
)
@Composable fun P(
attrs: AttrBuilderContext<HTMLParagraphElement>? = null,
content: ContentBuilder<HTMLParagraphElement>? = null
)
@Composable fun Span(
attrs: AttrBuilderContext<HTMLSpanElement>? = null,
content: ContentBuilder<HTMLSpanElement>? = null
)
@Composable fun Text(value: String)
/**
* Interactive elements
*/
@Composable fun Button(
attrs: AttrBuilderContext<HTMLButtonElement>? = null,
content: ContentBuilder<HTMLButtonElement>? = null
)
@Composable fun A(
href: String? = null,
attrs: AttrBuilderContext<HTMLAnchorElement>? = null,
content: ContentBuilder<HTMLAnchorElement>? = null
)
@Composable fun Label(
forId: String? = null,
attrs: AttrBuilderContext<HTMLLabelElement>? = null,
content: ContentBuilder<HTMLLabelElement>? = null
)Usage Example:
@Composable
fun WebPage() {
Div({ style { padding(20.px) } }) {
Header {
H1 { Text("My Web Application") }
Nav {
A(href = "/home") { Text("Home") }
A(href = "/about") { Text("About") }
A(href = "/contact") { Text("Contact") }
}
}
Main {
Section {
H2 { Text("Welcome") }
P { Text("This is a Compose for Web application.") }
Button({
onClick { event ->
console.log("Button clicked!")
}
}) {
Text("Click me")
}
}
}
Footer {
P { Text("© 2024 My Company") }
}
}
}Comprehensive form controls with controlled and uncontrolled input support.
/**
* Form container
*/
@Composable fun Form(
action: String? = null,
attrs: AttrBuilderContext<HTMLFormElement>? = null,
content: ContentBuilder<HTMLFormElement>? = null
)
/**
* Generic input element
*/
@Composable fun <K : Any> Input(
type: InputType<K>,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Controlled input elements
*/
@Composable fun TextInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
@Composable fun NumberInput(
value: Number? = null,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
@Composable fun EmailInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
@Composable fun PasswordInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
@Composable fun CheckboxInput(
checked: Boolean,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
@Composable fun RadioInput(
checked: Boolean,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
@Composable fun DateInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
@Composable fun DateTimeLocalInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
@Composable fun TimeInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
@Composable fun FileInput(
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
@Composable fun RangeInput(
value: Number,
min: Number? = null,
max: Number? = null,
step: Number? = null,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
@Composable fun SearchInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
@Composable fun TelInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
@Composable fun UrlInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Text area for multi-line input
*/
@Composable fun TextArea(
value: String,
attrs: AttrBuilderContext<HTMLTextAreaElement>? = null
)
/**
* Select dropdown
*/
@Composable fun Select(
attrs: AttrBuilderContext<HTMLSelectElement>? = null,
multiple: Boolean = false,
content: ContentBuilder<HTMLSelectElement>? = null
)
@Composable fun Option(
value: String,
attrs: AttrBuilderContext<HTMLOptionElement>? = null,
content: ContentBuilder<HTMLOptionElement>? = null
)
@Composable fun OptGroup(
label: String,
attrs: AttrBuilderContext<HTMLOptGroupElement>? = null,
content: ContentBuilder<HTMLOptGroupElement>? = null
)Usage Example:
@Composable
fun ContactForm() {
var name by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
var message by remember { mutableStateOf("") }
var newsletter by remember { mutableStateOf(false) }
Form {
Div({ style { marginBottom(16.px) } }) {
Label(forId = "name") { Text("Name:") }
TextInput(
value = name,
attrs = {
id("name")
onInput { event ->
name = event.value
}
}
)
}
Div({ style { marginBottom(16.px) } }) {
Label(forId = "email") { Text("Email:") }
EmailInput(
value = email,
attrs = {
id("email")
onInput { event ->
email = event.value
}
}
)
}
Div({ style { marginBottom(16.px) } }) {
Label(forId = "message") { Text("Message:") }
TextArea(
value = message,
attrs = {
id("message")
rows(5)
onInput { event ->
message = event.value
}
}
)
}
Div({ style { marginBottom(16.px) } }) {
CheckboxInput(
checked = newsletter,
attrs = {
id("newsletter")
onInput { event ->
newsletter = event.value
}
}
)
Label(forId = "newsletter") { Text("Subscribe to newsletter") }
}
Button({
type("submit")
onClick { event ->
event.preventDefault()
submitForm(name, email, message, newsletter)
}
}) {
Text("Send Message")
}
}
}Type-safe CSS styling with units, properties, and responsive design support.
/**
* Style scope for CSS properties
*/
interface StyleScope {
/** Add arbitrary CSS property */
fun property(propertyName: String, value: StylePropertyValue)
/** Set CSS custom property (variable) */
fun variable(variableName: String, value: StylePropertyValue)
}
/**
* Style scope builder implementation
*/
class StyleScopeBuilder : StyleScope, StyleHolder
/**
* Mount style tag with CSS rules
*/
@Composable fun Style(cssRules: CSSRuleDeclarationList)
/**
* Build and mount styles
*/
@Composable inline fun Style(rulesBuild: StyleSheetBuilder.() -> Unit)
/**
* CSS unit system
*/
interface CSSUnit
interface CSSUnitLength : CSSUnit
interface CSSUnitPercentage : CSSUnit
interface CSSUnitAngle : CSSUnit
interface CSSUnitTime : CSSUnit
/** Length units */
val Number.px: CSSLengthValue
val Number.em: CSSLengthValue
val Number.cssRem: CSSLengthValue
val Number.vw: CSSLengthValue
val Number.vh: CSSLengthValue
val Number.vmin: CSSLengthValue
val Number.vmax: CSSLengthValue
/** Percentage units */
val Number.percent: CSSPercentageValue
/** Angle units */
val Number.deg: CSSAngleValue
val Number.rad: CSSAngleValue
val Number.turn: CSSAngleValue
/** Time units */
val Number.s: CSSTimeValue
val Number.ms: CSSTimeValue
/**
* CSS variables
*/
class CSSStyleVariable<TValue : StylePropertyValue>
fun <TValue : StylePropertyValue> variable(): CSSStyleVariable<TValue>Usage Example:
@Composable
fun StyledComponents() {
Style {
// Global styles
".card" style {
backgroundColor(Color.white)
borderRadius(8.px)
boxShadow(0.px, 2.px, 4.px, rgba(0, 0, 0, 0.1))
padding(16.px)
margin(8.px)
}
".button-primary" style {
backgroundColor(Color.blue)
color(Color.white)
border(0.px)
borderRadius(4.px)
padding(12.px, 24.px)
cursor("pointer")
hover style {
backgroundColor(Color.darkBlue)
}
}
}
Div({ classes("card") }) {
H3({ style { marginTop(0.px) } }) {
Text("Styled Card")
}
P { Text("This card has custom styling applied.") }
Button({ classes("button-primary") }) {
Text("Primary Action")
}
}
}Comprehensive attribute and event handling system.
/**
* Attributes scope for element configuration
*/
interface AttrsScope<out TElement : Element> : EventsListenerScope {
/** Set arbitrary HTML attribute */
fun attr(attr: String, value: String)
/** Add inline styles */
fun style(builder: StyleScope.() -> Unit)
/** Add CSS classes */
fun classes(classes: Collection<String>)
fun classes(vararg classes: String)
/** Set DOM properties directly */
fun <E : HTMLElement, V> prop(update: (E, V) -> Unit, value: V)
/** Get element reference for imperative operations */
fun ref(effect: DisposableEffectScope.(TElement) -> DisposableEffectResult)
}
/**
* Common HTML attributes
*/
fun AttrsScope<*>.id(value: String)
fun AttrsScope<*>.title(value: String)
fun AttrsScope<*>.hidden()
fun AttrsScope<*>.tabIndex(value: Int)
fun AttrsScope<*>.contentEditable(value: Boolean)
fun AttrsScope<*>.draggable(value: Draggable)
/**
* Type aliases for attribute and content builders
*/
typealias AttrBuilderContext<T> = AttrsScope<T>.() -> Unit
typealias ContentBuilder<T> = @Composable ElementScope<T>.() -> UnitType-safe event handling with synthetic event wrappers.
/**
* Base synthetic event wrapper
*/
open class SyntheticEvent<Element : EventTarget>
/**
* Specific event types
*/
class SyntheticMouseEvent<Element : EventTarget> : SyntheticEvent<Element>
class SyntheticKeyboardEvent<Element : EventTarget> : SyntheticEvent<Element>
class SyntheticFocusEvent<Element : EventTarget> : SyntheticEvent<Element>
class SyntheticInputEvent<Element : EventTarget> : SyntheticEvent<Element>
class SyntheticChangeEvent<Element : EventTarget> : SyntheticEvent<Element>
class SyntheticSubmitEvent<Element : EventTarget> : SyntheticEvent<Element>
class SyntheticClipboardEvent<Element : EventTarget> : SyntheticEvent<Element>
class SyntheticTouchEvent<Element : EventTarget> : SyntheticEvent<Element>
/**
* Event listener scope
*/
interface EventsListenerScopeComplete SVG creation and manipulation API for vector graphics.
/**
* SVG container element
*/
@ExperimentalComposeWebSvgApi
@Composable fun Svg(
viewBox: String? = null,
attrs: AttrBuilderContext<SVGSVGElement>? = null,
content: ContentBuilder<SVGSVGElement>? = null
)
/**
* SVG shape elements
*/
@ExperimentalComposeWebSvgApi
@Composable fun ElementScope<SVGElement>.Circle(
cx: CSSLengthOrPercentageValue? = null,
cy: CSSLengthOrPercentageValue? = null,
r: CSSLengthOrPercentageValue? = null,
attrs: AttrBuilderContext<SVGCircleElement>? = null,
content: ContentBuilder<SVGCircleElement>? = null
)
@ExperimentalComposeWebSvgApi
@Composable fun ElementScope<SVGElement>.Rect(
x: CSSLengthOrPercentageValue? = null,
y: CSSLengthOrPercentageValue? = null,
width: CSSLengthOrPercentageValue? = null,
height: CSSLengthOrPercentageValue? = null,
attrs: AttrBuilderContext<SVGRectElement>? = null,
content: ContentBuilder<SVGRectElement>? = null
)
@ExperimentalComposeWebSvgApi
@Composable fun ElementScope<SVGElement>.Path(
d: String,
attrs: AttrBuilderContext<SVGPathElement>? = null,
content: ContentBuilder<SVGPathElement>? = null
)
@ExperimentalComposeWebSvgApi
@Composable fun ElementScope<SVGElement>.Line(
x1: CSSLengthOrPercentageValue? = null,
y1: CSSLengthOrPercentageValue? = null,
x2: CSSLengthOrPercentageValue? = null,
y2: CSSLengthOrPercentageValue? = null,
attrs: AttrBuilderContext<SVGLineElement>? = null,
content: ContentBuilder<SVGLineElement>? = null
)
@ExperimentalComposeWebSvgApi
@Composable fun ElementScope<SVGElement>.Ellipse(
cx: CSSLengthOrPercentageValue? = null,
cy: CSSLengthOrPercentageValue? = null,
rx: CSSLengthOrPercentageValue? = null,
ry: CSSLengthOrPercentageValue? = null,
attrs: AttrBuilderContext<SVGEllipseElement>? = null,
content: ContentBuilder<SVGEllipseElement>? = null
)
@ExperimentalComposeWebSvgApi
@Composable fun ElementScope<SVGElement>.Polygon(
points: String,
attrs: AttrBuilderContext<SVGPolygonElement>? = null,
content: ContentBuilder<SVGPolygonElement>? = null
)
@ExperimentalComposeWebSvgApi
@Composable fun ElementScope<SVGElement>.Polyline(
points: String,
attrs: AttrBuilderContext<SVGPolylineElement>? = null,
content: ContentBuilder<SVGPolylineElement>? = null
)
/**
* SVG text elements
*/
@ExperimentalComposeWebSvgApi
@Composable fun ElementScope<SVGElement>.SvgText(
text: String,
x: CSSLengthOrPercentageValue? = null,
y: CSSLengthOrPercentageValue? = null,
attrs: AttrBuilderContext<SVGTextElement>? = null
)
/**
* SVG gradient elements
*/
@ExperimentalComposeWebSvgApi
@Composable fun ElementScope<SVGElement>.LinearGradient(
id: String,
attrs: AttrBuilderContext<SVGLinearGradientElement>? = null,
content: ContentBuilder<SVGLinearGradientElement>? = null
)
@ExperimentalComposeWebSvgApi
@Composable fun ElementScope<SVGElement>.RadialGradient(
id: String,
attrs: AttrBuilderContext<SVGRadialGradientElement>? = null,
content: ContentBuilder<SVGRadialGradientElement>? = null
)
@ExperimentalComposeWebSvgApi
@Composable fun ElementScope<SVGElement>.Stop(
attrs: AttrBuilderContext<SVGStopElement>? = null,
content: ContentBuilder<SVGStopElement>? = null
)Usage Example:
@OptIn(ExperimentalComposeWebSvgApi::class)
@Composable
fun SvgExample() {
Svg(
viewBox = "0 0 200 200",
attrs = {
style {
width(200.px)
height(200.px)
border(1.px, LineStyle.Solid, Color.black)
}
}
) {
// Define gradient
LinearGradient(id = "myGradient") {
Stop {
attr("offset", "0%")
attr("stop-color", "red")
}
Stop {
attr("offset", "100%")
attr("stop-color", "blue")
}
}
// Draw shapes
Circle(
cx = 100.px,
cy = 100.px,
r = 50.px,
attrs = {
fill("url(#myGradient)")
attr("stroke", "black")
attr("stroke-width", "2")
}
)
Path(
d = "M 50 150 Q 100 100 150 150",
attrs = {
fill("none")
attr("stroke", "green")
attr("stroke-width", "3")
}
)
SvgText(
text = "SVG Text",
x = 100.px,
y = 180.px,
attrs = {
attr("text-anchor", "middle")
attr("font-family", "Arial")
attr("font-size", "16")
}
)
}
}Testing framework for web Compose applications with composition and DOM testing support.
/**
* Main test scope with coroutine support
*/
@ComposeWebExperimentalTestsApi
class TestScope : CoroutineScope
/**
* Execute test block with test scope
*/
@ComposeWebExperimentalTestsApi
fun runTest(block: suspend TestScope.() -> Unit)
/**
* Test scope API
*/
interface TestScope {
/** Root test element */
val root: HTMLElement
/** Create test composition */
fun composition(content: @Composable () -> Unit)
/** Get next child element */
fun nextChild(): HTMLElement
fun <T> nextChild(): T
/** Wait for recomposition to complete */
suspend fun waitForRecompositionComplete()
/** Wait for DOM changes */
suspend fun waitForChanges(elementId: String)
suspend fun waitForChanges(element: HTMLElement)
}
/**
* Test utilities
*/
val HTMLElement.computedStyle: CSSStyleDeclarationUsage Example:
@OptIn(ComposeWebExperimentalTestsApi::class)
fun testMyComponent() = runTest {
var clicked by mutableStateOf(false)
composition {
Button({
id("test-button")
onClick { clicked = true }
}) {
Text(if (clicked) "Clicked!" else "Click me")
}
}
val button = nextChild<HTMLButtonElement>()
assertEquals("Click me", button.textContent)
button.click()
waitForRecompositionComplete()
assertEquals("Clicked!", button.textContent)
assertTrue(clicked)
}/**
* Marks experimental web APIs
*/
@RequiresOptIn
annotation class ExperimentalComposeWebApi
/**
* Marks experimental style APIs
*/
@RequiresOptIn
annotation class ExperimentalComposeWebStyleApi
/**
* Marks experimental SVG APIs
*/
@RequiresOptIn
annotation class ExperimentalComposeWebSvgApi
/**
* Marks experimental test APIs
*/
@RequiresOptIn
annotation class ComposeWebExperimentalTestsApiInstall with Tessl CLI
npx tessl i tessl/maven-compose-multiplatform