CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-jetbrains-compose-foundation--foundation-wasm-js

Compose Multiplatform foundation library for building web UIs with type-safe HTML DSL, CSS-in-Kotlin, and event handling, compiled for WebAssembly/JavaScript target

Pending
Overview
Eval results
Files

form-controls.mddocs/

Form Controls

Specialized form input components providing controlled and uncontrolled patterns, comprehensive validation support, accessibility features, and seamless integration with Compose's state management.

Core Imports

import androidx.compose.runtime.*
import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.attributes.*
import org.jetbrains.compose.web.attributes.builders.*

Capabilities

Form Container

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 Input Controls

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")
                }
            }
        )
    }
}

Numeric Input Controls

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")
}

Choice Input Controls

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") }
    }
}

Date and Time Controls

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 Input Controls

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")
    }
}

Form Labels and Grouping

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 Controls

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")
        })
    }
}

Types

// 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

docs

css-styling.md

event-handling.md

form-controls.md

html-attributes.md

html-elements.md

index.md

tile.json