Compose Multiplatform foundation library for building web UIs with type-safe HTML DSL, CSS-in-Kotlin, and event handling, compiled for WebAssembly/JavaScript target
—
Specialized form input components providing controlled and uncontrolled patterns, comprehensive validation support, accessibility features, and seamless integration with Compose's state management.
import androidx.compose.runtime.*
import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.attributes.*
import org.jetbrains.compose.web.attributes.builders.*Form element for grouping and managing form controls with submission handling.
/**
* Form container element with submission and validation support
*/
@Composable
fun Form(
action: String? = null,
attrs: AttrBuilderContext<HTMLFormElement>? = null,
content: ContentBuilder<HTMLFormElement>? = null
)Usage Examples:
Form(
action = "/submit",
attrs = {
method(FormMethod.Post)
encType(FormEncType.MultipartFormData)
onSubmit { event ->
event.preventDefault()
if (validateForm()) {
submitFormData()
} else {
showValidationErrors()
}
}
}
) {
// Form controls go here
}Text-based input controls with controlled and uncontrolled patterns.
/**
* Controlled text input with reactive value binding
*/
@Composable
fun TextInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Generic input element with type specification
*/
@Composable
fun Input<K>(
type: InputType<K>,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Password input with masked characters
*/
@Composable
fun PasswordInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Email input with built-in validation
*/
@Composable
fun EmailInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* URL input with URL validation
*/
@Composable
fun UrlInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Telephone input
*/
@Composable
fun TelInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Search input with search styling
*/
@Composable
fun SearchInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Multi-line text input
*/
@Composable
fun TextArea(
value: String? = null,
attrs: AttrBuilderContext<HTMLTextAreaElement>? = null
)Usage Examples:
@Composable
fun ContactForm() {
var name by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
var message by remember { mutableStateOf("") }
var nameError by remember { mutableStateOf<String?>(null) }
Form {
// Text input with validation
TextInput(
value = name,
attrs = {
placeholder("Enter your name")
required()
maxLength(100)
onInput { event ->
name = event.value
nameError = if (name.length < 2) "Name too short" else null
}
onBlur {
if (name.isEmpty()) nameError = "Name is required"
}
// Conditional styling based on validation
style {
borderColor(if (nameError != null) Color.red else Color.gray)
}
}
)
nameError?.let { error ->
Span({ style { color(Color.red) } }) { Text(error) }
}
// Email input with validation
EmailInput(
value = email,
attrs = {
placeholder("your.email@example.com")
required()
onInput { event ->
email = event.value
}
onChange { event ->
// Validate email format on change
validateEmailFormat(event.value)
}
}
)
// Text area for longer text
TextArea(
value = message,
attrs = {
placeholder("Enter your message...")
rows(5)
maxLength(1000)
onInput { event ->
message = event.value
}
style {
width(100.percent)
resize("vertical")
}
}
)
}
}Input controls for numeric values with constraints and formatting.
/**
* Number input with numeric constraints
*/
@Composable
fun NumberInput(
value: Number? = null,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Range slider input
*/
@Composable
fun RangeInput(
value: Number? = null,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)Usage Examples:
@Composable
fun NumericInputs() {
var quantity by remember { mutableStateOf(1) }
var price by remember { mutableStateOf(0.0) }
var rating by remember { mutableStateOf(5) }
// Integer input with constraints
NumberInput(
value = quantity,
attrs = {
min("1")
max("99")
step(1)
onInput { event ->
quantity = event.value.toIntOrNull() ?: 1
}
style {
width(80.px)
}
}
)
// Decimal input
NumberInput(
value = price,
attrs = {
min("0")
step(0.01)
placeholder("0.00")
onInput { event ->
price = event.value.toDoubleOrNull() ?: 0.0
}
}
)
// Range slider
RangeInput(
value = rating,
attrs = {
min("1")
max("10")
step(1)
onInput { event ->
rating = event.value.toIntOrNull() ?: 5
}
style {
width(200.px)
}
}
)
Text("Rating: $rating/10")
}Input controls for selecting from predefined options.
/**
* Checkbox input for boolean choices
*/
@Composable
fun CheckboxInput(
checked: Boolean,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Radio button input for single choice from group
*/
@Composable
fun RadioInput(
checked: Boolean,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Select dropdown for single or multiple choice
*/
@Composable
fun Select(
attrs: AttrBuilderContext<HTMLSelectElement>? = null,
content: ContentBuilder<HTMLSelectElement>? = null
)
/**
* Option element for select dropdowns
*/
@Composable
fun Option(
value: String,
attrs: AttrBuilderContext<HTMLOptionElement>? = null,
content: ContentBuilder<HTMLOptionElement>? = null
)
/**
* Option group for organizing select options
*/
@Composable
fun OptGroup(
label: String,
attrs: AttrBuilderContext<HTMLOptGroupElement>? = null,
content: ContentBuilder<HTMLOptGroupElement>? = null
)Usage Examples:
@Composable
fun ChoiceInputs() {
var acceptTerms by remember { mutableStateOf(false) }
var notifications by remember { mutableStateOf(true) }
var theme by remember { mutableStateOf("light") }
var country by remember { mutableStateOf("") }
var languages by remember { mutableStateOf(setOf<String>()) }
// Checkboxes
Label {
CheckboxInput(
checked = acceptTerms,
attrs = {
required()
onChange { event ->
acceptTerms = event.target.checked
}
}
)
Text(" I accept the terms and conditions")
}
Label {
CheckboxInput(
checked = notifications,
attrs = {
onChange { event ->
notifications = event.target.checked
}
}
)
Text(" Enable notifications")
}
// Radio buttons
Fieldset {
Legend { Text("Theme Preference") }
listOf("light", "dark", "auto").forEach { themeOption ->
Label({
style {
display(DisplayStyle.block)
margin(4.px, 0.px)
}
}) {
RadioInput(
checked = theme == themeOption,
attrs = {
name("theme")
value(themeOption)
onChange { event ->
if (event.target.checked) {
theme = themeOption
}
}
}
)
Text(" ${themeOption.capitalize()}")
}
}
}
// Select dropdown
Label {
Text("Country:")
Select({
value(country)
onChange { event ->
country = event.target.value
}
}) {
Option("", { disabled() }) { Text("Select a country") }
OptGroup("North America") {
Option("us") { Text("United States") }
Option("ca") { Text("Canada") }
Option("mx") { Text("Mexico") }
}
OptGroup("Europe") {
Option("uk") { Text("United Kingdom") }
Option("de") { Text("Germany") }
Option("fr") { Text("France") }
}
}
}
// Multi-select
Select({
multiple()
size(4)
onChange { event ->
val selected = event.target.selectedOptions
languages = (0 until selected.length)
.mapNotNull { selected.item(it)?.value }
.toSet()
}
}) {
Option("en") { Text("English") }
Option("es") { Text("Spanish") }
Option("fr") { Text("French") }
Option("de") { Text("German") }
Option("zh") { Text("Chinese") }
}
}Input controls for date and time selection with various formats.
/**
* Date input (YYYY-MM-DD format)
*/
@Composable
fun DateInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Time input (HH:MM format)
*/
@Composable
fun TimeInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* DateTime-local input (YYYY-MM-DDTHH:MM format)
*/
@Composable
fun DateTimeLocalInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Week input (YYYY-W## format)
*/
@Composable
fun WeekInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Month input (YYYY-MM format)
*/
@Composable
fun MonthInput(
value: String,
attrs: AttrBuilderContext<HTMLInputElement>? = null
)Usage Examples:
@Composable
fun DateTimeInputs() {
var birthDate by remember { mutableStateOf("") }
var appointmentTime by remember { mutableStateOf("") }
var eventDateTime by remember { mutableStateOf("") }
var vacationWeek by remember { mutableStateOf("") }
var expenseMonth by remember { mutableStateOf("") }
// Date picker
Label {
Text("Birth Date:")
DateInput(
value = birthDate,
attrs = {
max("2010-12-31") // Max age constraint
onInput { event ->
birthDate = event.value
}
}
)
}
// Time picker
Label {
Text("Appointment Time:")
TimeInput(
value = appointmentTime,
attrs = {
min("09:00")
max("17:00")
step(900) // 15-minute intervals
onInput { event ->
appointmentTime = event.value
}
}
)
}
// DateTime picker
Label {
Text("Event Date & Time:")
DateTimeLocalInput(
value = eventDateTime,
attrs = {
min(getCurrentDateTime())
onInput { event ->
eventDateTime = event.value
}
}
)
}
// Week picker
Label {
Text("Vacation Week:")
WeekInput(
value = vacationWeek,
attrs = {
onInput { event ->
vacationWeek = event.value
}
}
)
}
// Month picker
Label {
Text("Expense Month:")
MonthInput(
value = expenseMonth,
attrs = {
max(getCurrentMonth())
onInput { event ->
expenseMonth = event.value
}
}
)
}
}File selection and upload controls with type filtering and multiple file support.
/**
* File input for file selection and upload
*/
@Composable
fun FileInput(
attrs: AttrBuilderContext<HTMLInputElement>? = null
)Usage Examples:
@Composable
fun FileInputs() {
var selectedFiles by remember { mutableStateOf<FileList?>(null) }
var imageFile by remember { mutableStateOf<File?>(null) }
var documentFiles by remember { mutableStateOf<List<File>>(emptyList()) }
// Single image file
Label {
Text("Profile Picture:")
FileInput(
attrs = {
accept("image/*")
onChange { event ->
imageFile = event.target.files?.item(0)
}
}
)
}
imageFile?.let { file ->
Text("Selected: ${file.name} (${file.size} bytes)")
}
// Multiple document files
Label {
Text("Upload Documents:")
FileInput(
attrs = {
accept(".pdf,.doc,.docx,.txt")
multiple()
onChange { event ->
val files = event.target.files
documentFiles = (0 until (files?.length ?: 0))
.mapNotNull { files?.item(it) }
}
}
)
}
if (documentFiles.isNotEmpty()) {
Ul {
documentFiles.forEach { file ->
Li { Text("${file.name} - ${formatFileSize(file.size)}") }
}
}
}
// Generic file input with drag and drop styling
FileInput(
attrs = {
style {
display(DisplayStyle.none) // Hide default input
}
onChange { event ->
selectedFiles = event.target.files
handleFileSelection(selectedFiles)
}
}
)
// Custom styled drop zone
Div({
style {
border(2.px, "dashed", Color.gray)
borderRadius(8.px)
padding(40.px)
textAlign("center")
cursor("pointer")
transition("border-color 200ms")
}
onClick {
// Trigger hidden file input
triggerFileSelection()
}
onDragOver { event ->
event.preventDefault()
event.currentTarget.style.borderColor = "blue"
}
onDragLeave { event ->
event.currentTarget.style.borderColor = "gray"
}
onDrop { event ->
event.preventDefault()
event.currentTarget.style.borderColor = "gray"
handleDroppedFiles(event.dataTransfer?.files)
}
}) {
Text("Drop files here or click to select")
}
}Elements for organizing and labeling form controls with accessibility support.
/**
* Label element for form controls
*/
@Composable
fun Label(
forId: String? = null,
attrs: AttrBuilderContext<HTMLLabelElement>? = null,
content: ContentBuilder<HTMLLabelElement>? = null
)
/**
* Fieldset for grouping related form controls
*/
@Composable
fun Fieldset(
attrs: AttrBuilderContext<HTMLFieldSetElement>? = null,
content: ContentBuilder<HTMLFieldSetElement>? = null
)
/**
* Legend for fieldset title
*/
@Composable
fun Legend(
attrs: AttrBuilderContext<HTMLLegendElement>? = null,
content: ContentBuilder<HTMLLegendElement>? = null
)
/**
* Datalist for input suggestions
*/
@Composable
fun Datalist(
attrs: AttrBuilderContext<HTMLDataListElement>? = null,
content: ContentBuilder<HTMLDataListElement>? = null
)
/**
* Output element for calculation results
*/
@Composable
fun Output(
attrs: AttrBuilderContext<HTMLOutputElement>? = null,
content: ContentBuilder<HTMLOutputElement>? = null
)Usage Examples:
@Composable
fun FormGrouping() {
var firstName by remember { mutableStateOf("") }
var lastName by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
var phone by remember { mutableStateOf("") }
var city by remember { mutableStateOf("") }
Form {
// Personal information fieldset
Fieldset {
Legend { Text("Personal Information") }
Label(forId = "firstName") {
Text("First Name: ")
}
TextInput(
value = firstName,
attrs = {
id("firstName")
required()
onInput { event -> firstName = event.value }
}
)
Label(forId = "lastName") {
Text("Last Name: ")
}
TextInput(
value = lastName,
attrs = {
id("lastName")
required()
onInput { event -> lastName = event.value }
}
)
}
// Contact information fieldset
Fieldset {
Legend { Text("Contact Information") }
Label(forId = "email") { Text("Email: ") }
EmailInput(
value = email,
attrs = {
id("email")
required()
onInput { event -> email = event.value }
}
)
Label(forId = "phone") { Text("Phone: ") }
TelInput(
value = phone,
attrs = {
id("phone")
onInput { event -> phone = event.value }
}
)
}
// City input with datalist suggestions
Label(forId = "city") { Text("City: ") }
TextInput(
value = city,
attrs = {
id("city")
list("cities")
onInput { event -> city = event.value }
}
)
Datalist(attrs = { id("cities") }) {
Option("New York") { Text("New York") }
Option("Los Angeles") { Text("Los Angeles") }
Option("Chicago") { Text("Chicago") }
Option("Houston") { Text("Houston") }
Option("Phoenix") { Text("Phoenix") }
}
// Output for calculated field
Output({
for_("firstName lastName")
name("fullName")
}) {
Text("Full Name: $firstName $lastName")
}
}
}Button elements for form actions and user interactions.
/**
* Button element
*/
@Composable
fun Button(
attrs: AttrBuilderContext<HTMLButtonElement>? = null,
content: ContentBuilder<HTMLButtonElement>? = null
)
/**
* Submit input button
*/
@Composable
fun SubmitInput(
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Reset input button
*/
@Composable
fun ResetInput(
attrs: AttrBuilderContext<HTMLInputElement>? = null
)
/**
* Hidden input for form data
*/
@Composable
fun HiddenInput(
attrs: AttrBuilderContext<HTMLInputElement>? = null
)Usage Examples:
@Composable
fun FormButtons() {
var isSubmitting by remember { mutableStateOf(false) }
Form({
onSubmit { event ->
event.preventDefault()
isSubmitting = true
submitForm()
}
}) {
// Form fields...
Div({
style {
display(DisplayStyle.flex)
gap(12.px)
marginTop(20.px)
}
}) {
// Primary submit button
Button({
type(ButtonType.Submit)
disabled(isSubmitting)
style {
backgroundColor(if (isSubmitting) Color.gray else Color.blue)
color(Color.white)
border(0.px)
padding(12.px, 24.px)
borderRadius(4.px)
cursor(if (isSubmitting) "not-allowed" else "pointer")
}
}) {
Text(if (isSubmitting) "Submitting..." else "Submit")
}
// Reset button
Button({
type(ButtonType.Reset)
style {
backgroundColor(Color.lightgray)
color(Color.black)
border(1.px, "solid", Color.gray)
padding(12.px, 24.px)
borderRadius(4.px)
cursor("pointer")
}
onClick {
if (confirm("Reset all form data?")) {
resetForm()
}
}
}) {
Text("Reset")
}
// Cancel button
Button({
type(ButtonType.Button)
onClick {
navigateBack()
}
style {
backgroundColor("transparent".unsafeCast<CSSColorValue>())
color(Color.blue)
border(1.px, "solid", Color.blue)
padding(12.px, 24.px)
borderRadius(4.px)
cursor("pointer")
}
}) {
Text("Cancel")
}
}
// Hidden inputs for additional data
HiddenInput(attrs = {
name("csrf_token")
value(getCsrfToken())
})
HiddenInput(attrs = {
name("form_version")
value("1.2")
})
}
}// File API types
external interface FileList {
val length: Int
fun item(index: Int): File?
}
external interface File {
val name: String
val size: Long
val type: String
val lastModified: Long
}
// Form validation
interface ValidityState {
val valid: Boolean
val badInput: Boolean
val customError: Boolean
val patternMismatch: Boolean
val rangeOverflow: Boolean
val rangeUnderflow: Boolean
val stepMismatch: Boolean
val tooLong: Boolean
val tooShort: Boolean
val typeMismatch: Boolean
val valueMissing: Boolean
}Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-compose-foundation--foundation-wasm-js