0
# Form Controls
1
2
Specialized form input components providing controlled and uncontrolled patterns, comprehensive validation support, accessibility features, and seamless integration with Compose's state management.
3
4
## Core Imports
5
6
```kotlin
7
import androidx.compose.runtime.*
8
import org.jetbrains.compose.web.dom.*
9
import org.jetbrains.compose.web.attributes.*
10
import org.jetbrains.compose.web.attributes.builders.*
11
```
12
13
## Capabilities
14
15
### Form Container
16
17
Form element for grouping and managing form controls with submission handling.
18
19
```kotlin { .api }
20
/**
21
* Form container element with submission and validation support
22
*/
23
@Composable
24
fun Form(
25
action: String? = null,
26
attrs: AttrBuilderContext<HTMLFormElement>? = null,
27
content: ContentBuilder<HTMLFormElement>? = null
28
)
29
```
30
31
**Usage Examples:**
32
33
```kotlin
34
Form(
35
action = "/submit",
36
attrs = {
37
method(FormMethod.Post)
38
encType(FormEncType.MultipartFormData)
39
40
onSubmit { event ->
41
event.preventDefault()
42
43
if (validateForm()) {
44
submitFormData()
45
} else {
46
showValidationErrors()
47
}
48
}
49
}
50
) {
51
// Form controls go here
52
}
53
```
54
55
### Text Input Controls
56
57
Text-based input controls with controlled and uncontrolled patterns.
58
59
```kotlin { .api }
60
/**
61
* Controlled text input with reactive value binding
62
*/
63
@Composable
64
fun TextInput(
65
value: String,
66
attrs: AttrBuilderContext<HTMLInputElement>? = null
67
)
68
69
/**
70
* Generic input element with type specification
71
*/
72
@Composable
73
fun Input<K>(
74
type: InputType<K>,
75
attrs: AttrBuilderContext<HTMLInputElement>? = null
76
)
77
78
/**
79
* Password input with masked characters
80
*/
81
@Composable
82
fun PasswordInput(
83
value: String,
84
attrs: AttrBuilderContext<HTMLInputElement>? = null
85
)
86
87
/**
88
* Email input with built-in validation
89
*/
90
@Composable
91
fun EmailInput(
92
value: String,
93
attrs: AttrBuilderContext<HTMLInputElement>? = null
94
)
95
96
/**
97
* URL input with URL validation
98
*/
99
@Composable
100
fun UrlInput(
101
value: String,
102
attrs: AttrBuilderContext<HTMLInputElement>? = null
103
)
104
105
/**
106
* Telephone input
107
*/
108
@Composable
109
fun TelInput(
110
value: String,
111
attrs: AttrBuilderContext<HTMLInputElement>? = null
112
)
113
114
/**
115
* Search input with search styling
116
*/
117
@Composable
118
fun SearchInput(
119
value: String,
120
attrs: AttrBuilderContext<HTMLInputElement>? = null
121
)
122
123
/**
124
* Multi-line text input
125
*/
126
@Composable
127
fun TextArea(
128
value: String? = null,
129
attrs: AttrBuilderContext<HTMLTextAreaElement>? = null
130
)
131
```
132
133
**Usage Examples:**
134
135
```kotlin
136
@Composable
137
fun ContactForm() {
138
var name by remember { mutableStateOf("") }
139
var email by remember { mutableStateOf("") }
140
var message by remember { mutableStateOf("") }
141
var nameError by remember { mutableStateOf<String?>(null) }
142
143
Form {
144
// Text input with validation
145
TextInput(
146
value = name,
147
attrs = {
148
placeholder("Enter your name")
149
required()
150
maxLength(100)
151
152
onInput { event ->
153
name = event.value
154
nameError = if (name.length < 2) "Name too short" else null
155
}
156
157
onBlur {
158
if (name.isEmpty()) nameError = "Name is required"
159
}
160
161
// Conditional styling based on validation
162
style {
163
borderColor(if (nameError != null) Color.red else Color.gray)
164
}
165
}
166
)
167
168
nameError?.let { error ->
169
Span({ style { color(Color.red) } }) { Text(error) }
170
}
171
172
// Email input with validation
173
EmailInput(
174
value = email,
175
attrs = {
176
placeholder("your.email@example.com")
177
required()
178
179
onInput { event ->
180
email = event.value
181
}
182
183
onChange { event ->
184
// Validate email format on change
185
validateEmailFormat(event.value)
186
}
187
}
188
)
189
190
// Text area for longer text
191
TextArea(
192
value = message,
193
attrs = {
194
placeholder("Enter your message...")
195
rows(5)
196
maxLength(1000)
197
198
onInput { event ->
199
message = event.value
200
}
201
202
style {
203
width(100.percent)
204
resize("vertical")
205
}
206
}
207
)
208
}
209
}
210
```
211
212
### Numeric Input Controls
213
214
Input controls for numeric values with constraints and formatting.
215
216
```kotlin { .api }
217
/**
218
* Number input with numeric constraints
219
*/
220
@Composable
221
fun NumberInput(
222
value: Number? = null,
223
attrs: AttrBuilderContext<HTMLInputElement>? = null
224
)
225
226
/**
227
* Range slider input
228
*/
229
@Composable
230
fun RangeInput(
231
value: Number? = null,
232
attrs: AttrBuilderContext<HTMLInputElement>? = null
233
)
234
```
235
236
**Usage Examples:**
237
238
```kotlin
239
@Composable
240
fun NumericInputs() {
241
var quantity by remember { mutableStateOf(1) }
242
var price by remember { mutableStateOf(0.0) }
243
var rating by remember { mutableStateOf(5) }
244
245
// Integer input with constraints
246
NumberInput(
247
value = quantity,
248
attrs = {
249
min("1")
250
max("99")
251
step(1)
252
253
onInput { event ->
254
quantity = event.value.toIntOrNull() ?: 1
255
}
256
257
style {
258
width(80.px)
259
}
260
}
261
)
262
263
// Decimal input
264
NumberInput(
265
value = price,
266
attrs = {
267
min("0")
268
step(0.01)
269
placeholder("0.00")
270
271
onInput { event ->
272
price = event.value.toDoubleOrNull() ?: 0.0
273
}
274
}
275
)
276
277
// Range slider
278
RangeInput(
279
value = rating,
280
attrs = {
281
min("1")
282
max("10")
283
step(1)
284
285
onInput { event ->
286
rating = event.value.toIntOrNull() ?: 5
287
}
288
289
style {
290
width(200.px)
291
}
292
}
293
)
294
295
Text("Rating: $rating/10")
296
}
297
```
298
299
### Choice Input Controls
300
301
Input controls for selecting from predefined options.
302
303
```kotlin { .api }
304
/**
305
* Checkbox input for boolean choices
306
*/
307
@Composable
308
fun CheckboxInput(
309
checked: Boolean,
310
attrs: AttrBuilderContext<HTMLInputElement>? = null
311
)
312
313
/**
314
* Radio button input for single choice from group
315
*/
316
@Composable
317
fun RadioInput(
318
checked: Boolean,
319
attrs: AttrBuilderContext<HTMLInputElement>? = null
320
)
321
322
/**
323
* Select dropdown for single or multiple choice
324
*/
325
@Composable
326
fun Select(
327
attrs: AttrBuilderContext<HTMLSelectElement>? = null,
328
content: ContentBuilder<HTMLSelectElement>? = null
329
)
330
331
/**
332
* Option element for select dropdowns
333
*/
334
@Composable
335
fun Option(
336
value: String,
337
attrs: AttrBuilderContext<HTMLOptionElement>? = null,
338
content: ContentBuilder<HTMLOptionElement>? = null
339
)
340
341
/**
342
* Option group for organizing select options
343
*/
344
@Composable
345
fun OptGroup(
346
label: String,
347
attrs: AttrBuilderContext<HTMLOptGroupElement>? = null,
348
content: ContentBuilder<HTMLOptGroupElement>? = null
349
)
350
```
351
352
**Usage Examples:**
353
354
```kotlin
355
@Composable
356
fun ChoiceInputs() {
357
var acceptTerms by remember { mutableStateOf(false) }
358
var notifications by remember { mutableStateOf(true) }
359
var theme by remember { mutableStateOf("light") }
360
var country by remember { mutableStateOf("") }
361
var languages by remember { mutableStateOf(setOf<String>()) }
362
363
// Checkboxes
364
Label {
365
CheckboxInput(
366
checked = acceptTerms,
367
attrs = {
368
required()
369
onChange { event ->
370
acceptTerms = event.target.checked
371
}
372
}
373
)
374
Text(" I accept the terms and conditions")
375
}
376
377
Label {
378
CheckboxInput(
379
checked = notifications,
380
attrs = {
381
onChange { event ->
382
notifications = event.target.checked
383
}
384
}
385
)
386
Text(" Enable notifications")
387
}
388
389
// Radio buttons
390
Fieldset {
391
Legend { Text("Theme Preference") }
392
393
listOf("light", "dark", "auto").forEach { themeOption ->
394
Label({
395
style {
396
display(DisplayStyle.block)
397
margin(4.px, 0.px)
398
}
399
}) {
400
RadioInput(
401
checked = theme == themeOption,
402
attrs = {
403
name("theme")
404
value(themeOption)
405
onChange { event ->
406
if (event.target.checked) {
407
theme = themeOption
408
}
409
}
410
}
411
)
412
Text(" ${themeOption.capitalize()}")
413
}
414
}
415
}
416
417
// Select dropdown
418
Label {
419
Text("Country:")
420
Select({
421
value(country)
422
onChange { event ->
423
country = event.target.value
424
}
425
}) {
426
Option("", { disabled() }) { Text("Select a country") }
427
428
OptGroup("North America") {
429
Option("us") { Text("United States") }
430
Option("ca") { Text("Canada") }
431
Option("mx") { Text("Mexico") }
432
}
433
434
OptGroup("Europe") {
435
Option("uk") { Text("United Kingdom") }
436
Option("de") { Text("Germany") }
437
Option("fr") { Text("France") }
438
}
439
}
440
}
441
442
// Multi-select
443
Select({
444
multiple()
445
size(4)
446
onChange { event ->
447
val selected = event.target.selectedOptions
448
languages = (0 until selected.length)
449
.mapNotNull { selected.item(it)?.value }
450
.toSet()
451
}
452
}) {
453
Option("en") { Text("English") }
454
Option("es") { Text("Spanish") }
455
Option("fr") { Text("French") }
456
Option("de") { Text("German") }
457
Option("zh") { Text("Chinese") }
458
}
459
}
460
```
461
462
### Date and Time Controls
463
464
Input controls for date and time selection with various formats.
465
466
```kotlin { .api }
467
/**
468
* Date input (YYYY-MM-DD format)
469
*/
470
@Composable
471
fun DateInput(
472
value: String,
473
attrs: AttrBuilderContext<HTMLInputElement>? = null
474
)
475
476
/**
477
* Time input (HH:MM format)
478
*/
479
@Composable
480
fun TimeInput(
481
value: String,
482
attrs: AttrBuilderContext<HTMLInputElement>? = null
483
)
484
485
/**
486
* DateTime-local input (YYYY-MM-DDTHH:MM format)
487
*/
488
@Composable
489
fun DateTimeLocalInput(
490
value: String,
491
attrs: AttrBuilderContext<HTMLInputElement>? = null
492
)
493
494
/**
495
* Week input (YYYY-W## format)
496
*/
497
@Composable
498
fun WeekInput(
499
value: String,
500
attrs: AttrBuilderContext<HTMLInputElement>? = null
501
)
502
503
/**
504
* Month input (YYYY-MM format)
505
*/
506
@Composable
507
fun MonthInput(
508
value: String,
509
attrs: AttrBuilderContext<HTMLInputElement>? = null
510
)
511
```
512
513
**Usage Examples:**
514
515
```kotlin
516
@Composable
517
fun DateTimeInputs() {
518
var birthDate by remember { mutableStateOf("") }
519
var appointmentTime by remember { mutableStateOf("") }
520
var eventDateTime by remember { mutableStateOf("") }
521
var vacationWeek by remember { mutableStateOf("") }
522
var expenseMonth by remember { mutableStateOf("") }
523
524
// Date picker
525
Label {
526
Text("Birth Date:")
527
DateInput(
528
value = birthDate,
529
attrs = {
530
max("2010-12-31") // Max age constraint
531
onInput { event ->
532
birthDate = event.value
533
}
534
}
535
)
536
}
537
538
// Time picker
539
Label {
540
Text("Appointment Time:")
541
TimeInput(
542
value = appointmentTime,
543
attrs = {
544
min("09:00")
545
max("17:00")
546
step(900) // 15-minute intervals
547
onInput { event ->
548
appointmentTime = event.value
549
}
550
}
551
)
552
}
553
554
// DateTime picker
555
Label {
556
Text("Event Date & Time:")
557
DateTimeLocalInput(
558
value = eventDateTime,
559
attrs = {
560
min(getCurrentDateTime())
561
onInput { event ->
562
eventDateTime = event.value
563
}
564
}
565
)
566
}
567
568
// Week picker
569
Label {
570
Text("Vacation Week:")
571
WeekInput(
572
value = vacationWeek,
573
attrs = {
574
onInput { event ->
575
vacationWeek = event.value
576
}
577
}
578
)
579
}
580
581
// Month picker
582
Label {
583
Text("Expense Month:")
584
MonthInput(
585
value = expenseMonth,
586
attrs = {
587
max(getCurrentMonth())
588
onInput { event ->
589
expenseMonth = event.value
590
}
591
}
592
)
593
}
594
}
595
```
596
597
### File Input Controls
598
599
File selection and upload controls with type filtering and multiple file support.
600
601
```kotlin { .api }
602
/**
603
* File input for file selection and upload
604
*/
605
@Composable
606
fun FileInput(
607
attrs: AttrBuilderContext<HTMLInputElement>? = null
608
)
609
```
610
611
**Usage Examples:**
612
613
```kotlin
614
@Composable
615
fun FileInputs() {
616
var selectedFiles by remember { mutableStateOf<FileList?>(null) }
617
var imageFile by remember { mutableStateOf<File?>(null) }
618
var documentFiles by remember { mutableStateOf<List<File>>(emptyList()) }
619
620
// Single image file
621
Label {
622
Text("Profile Picture:")
623
FileInput(
624
attrs = {
625
accept("image/*")
626
onChange { event ->
627
imageFile = event.target.files?.item(0)
628
}
629
}
630
)
631
}
632
633
imageFile?.let { file ->
634
Text("Selected: ${file.name} (${file.size} bytes)")
635
}
636
637
// Multiple document files
638
Label {
639
Text("Upload Documents:")
640
FileInput(
641
attrs = {
642
accept(".pdf,.doc,.docx,.txt")
643
multiple()
644
onChange { event ->
645
val files = event.target.files
646
documentFiles = (0 until (files?.length ?: 0))
647
.mapNotNull { files?.item(it) }
648
}
649
}
650
)
651
}
652
653
if (documentFiles.isNotEmpty()) {
654
Ul {
655
documentFiles.forEach { file ->
656
Li { Text("${file.name} - ${formatFileSize(file.size)}") }
657
}
658
}
659
}
660
661
// Generic file input with drag and drop styling
662
FileInput(
663
attrs = {
664
style {
665
display(DisplayStyle.none) // Hide default input
666
}
667
668
onChange { event ->
669
selectedFiles = event.target.files
670
handleFileSelection(selectedFiles)
671
}
672
}
673
)
674
675
// Custom styled drop zone
676
Div({
677
style {
678
border(2.px, "dashed", Color.gray)
679
borderRadius(8.px)
680
padding(40.px)
681
textAlign("center")
682
cursor("pointer")
683
transition("border-color 200ms")
684
}
685
686
onClick {
687
// Trigger hidden file input
688
triggerFileSelection()
689
}
690
691
onDragOver { event ->
692
event.preventDefault()
693
event.currentTarget.style.borderColor = "blue"
694
}
695
696
onDragLeave { event ->
697
event.currentTarget.style.borderColor = "gray"
698
}
699
700
onDrop { event ->
701
event.preventDefault()
702
event.currentTarget.style.borderColor = "gray"
703
handleDroppedFiles(event.dataTransfer?.files)
704
}
705
}) {
706
Text("Drop files here or click to select")
707
}
708
}
709
```
710
711
### Form Labels and Grouping
712
713
Elements for organizing and labeling form controls with accessibility support.
714
715
```kotlin { .api }
716
/**
717
* Label element for form controls
718
*/
719
@Composable
720
fun Label(
721
forId: String? = null,
722
attrs: AttrBuilderContext<HTMLLabelElement>? = null,
723
content: ContentBuilder<HTMLLabelElement>? = null
724
)
725
726
/**
727
* Fieldset for grouping related form controls
728
*/
729
@Composable
730
fun Fieldset(
731
attrs: AttrBuilderContext<HTMLFieldSetElement>? = null,
732
content: ContentBuilder<HTMLFieldSetElement>? = null
733
)
734
735
/**
736
* Legend for fieldset title
737
*/
738
@Composable
739
fun Legend(
740
attrs: AttrBuilderContext<HTMLLegendElement>? = null,
741
content: ContentBuilder<HTMLLegendElement>? = null
742
)
743
744
/**
745
* Datalist for input suggestions
746
*/
747
@Composable
748
fun Datalist(
749
attrs: AttrBuilderContext<HTMLDataListElement>? = null,
750
content: ContentBuilder<HTMLDataListElement>? = null
751
)
752
753
/**
754
* Output element for calculation results
755
*/
756
@Composable
757
fun Output(
758
attrs: AttrBuilderContext<HTMLOutputElement>? = null,
759
content: ContentBuilder<HTMLOutputElement>? = null
760
)
761
```
762
763
**Usage Examples:**
764
765
```kotlin
766
@Composable
767
fun FormGrouping() {
768
var firstName by remember { mutableStateOf("") }
769
var lastName by remember { mutableStateOf("") }
770
var email by remember { mutableStateOf("") }
771
var phone by remember { mutableStateOf("") }
772
var city by remember { mutableStateOf("") }
773
774
Form {
775
// Personal information fieldset
776
Fieldset {
777
Legend { Text("Personal Information") }
778
779
Label(forId = "firstName") {
780
Text("First Name: ")
781
}
782
TextInput(
783
value = firstName,
784
attrs = {
785
id("firstName")
786
required()
787
onInput { event -> firstName = event.value }
788
}
789
)
790
791
Label(forId = "lastName") {
792
Text("Last Name: ")
793
}
794
TextInput(
795
value = lastName,
796
attrs = {
797
id("lastName")
798
required()
799
onInput { event -> lastName = event.value }
800
}
801
)
802
}
803
804
// Contact information fieldset
805
Fieldset {
806
Legend { Text("Contact Information") }
807
808
Label(forId = "email") { Text("Email: ") }
809
EmailInput(
810
value = email,
811
attrs = {
812
id("email")
813
required()
814
onInput { event -> email = event.value }
815
}
816
)
817
818
Label(forId = "phone") { Text("Phone: ") }
819
TelInput(
820
value = phone,
821
attrs = {
822
id("phone")
823
onInput { event -> phone = event.value }
824
}
825
)
826
}
827
828
// City input with datalist suggestions
829
Label(forId = "city") { Text("City: ") }
830
TextInput(
831
value = city,
832
attrs = {
833
id("city")
834
list("cities")
835
onInput { event -> city = event.value }
836
}
837
)
838
839
Datalist(attrs = { id("cities") }) {
840
Option("New York") { Text("New York") }
841
Option("Los Angeles") { Text("Los Angeles") }
842
Option("Chicago") { Text("Chicago") }
843
Option("Houston") { Text("Houston") }
844
Option("Phoenix") { Text("Phoenix") }
845
}
846
847
// Output for calculated field
848
Output({
849
for_("firstName lastName")
850
name("fullName")
851
}) {
852
Text("Full Name: $firstName $lastName")
853
}
854
}
855
}
856
```
857
858
### Button Controls
859
860
Button elements for form actions and user interactions.
861
862
```kotlin { .api }
863
/**
864
* Button element
865
*/
866
@Composable
867
fun Button(
868
attrs: AttrBuilderContext<HTMLButtonElement>? = null,
869
content: ContentBuilder<HTMLButtonElement>? = null
870
)
871
872
/**
873
* Submit input button
874
*/
875
@Composable
876
fun SubmitInput(
877
attrs: AttrBuilderContext<HTMLInputElement>? = null
878
)
879
880
/**
881
* Reset input button
882
*/
883
@Composable
884
fun ResetInput(
885
attrs: AttrBuilderContext<HTMLInputElement>? = null
886
)
887
888
/**
889
* Hidden input for form data
890
*/
891
@Composable
892
fun HiddenInput(
893
attrs: AttrBuilderContext<HTMLInputElement>? = null
894
)
895
```
896
897
**Usage Examples:**
898
899
```kotlin
900
@Composable
901
fun FormButtons() {
902
var isSubmitting by remember { mutableStateOf(false) }
903
904
Form({
905
onSubmit { event ->
906
event.preventDefault()
907
isSubmitting = true
908
submitForm()
909
}
910
}) {
911
// Form fields...
912
913
Div({
914
style {
915
display(DisplayStyle.flex)
916
gap(12.px)
917
marginTop(20.px)
918
}
919
}) {
920
// Primary submit button
921
Button({
922
type(ButtonType.Submit)
923
disabled(isSubmitting)
924
925
style {
926
backgroundColor(if (isSubmitting) Color.gray else Color.blue)
927
color(Color.white)
928
border(0.px)
929
padding(12.px, 24.px)
930
borderRadius(4.px)
931
cursor(if (isSubmitting) "not-allowed" else "pointer")
932
}
933
}) {
934
Text(if (isSubmitting) "Submitting..." else "Submit")
935
}
936
937
// Reset button
938
Button({
939
type(ButtonType.Reset)
940
941
style {
942
backgroundColor(Color.lightgray)
943
color(Color.black)
944
border(1.px, "solid", Color.gray)
945
padding(12.px, 24.px)
946
borderRadius(4.px)
947
cursor("pointer")
948
}
949
950
onClick {
951
if (confirm("Reset all form data?")) {
952
resetForm()
953
}
954
}
955
}) {
956
Text("Reset")
957
}
958
959
// Cancel button
960
Button({
961
type(ButtonType.Button)
962
963
onClick {
964
navigateBack()
965
}
966
967
style {
968
backgroundColor("transparent".unsafeCast<CSSColorValue>())
969
color(Color.blue)
970
border(1.px, "solid", Color.blue)
971
padding(12.px, 24.px)
972
borderRadius(4.px)
973
cursor("pointer")
974
}
975
}) {
976
Text("Cancel")
977
}
978
}
979
980
// Hidden inputs for additional data
981
HiddenInput(attrs = {
982
name("csrf_token")
983
value(getCsrfToken())
984
})
985
986
HiddenInput(attrs = {
987
name("form_version")
988
value("1.2")
989
})
990
}
991
}
992
```
993
994
## Types
995
996
```kotlin { .api }
997
// File API types
998
external interface FileList {
999
val length: Int
1000
fun item(index: Int): File?
1001
}
1002
1003
external interface File {
1004
val name: String
1005
val size: Long
1006
val type: String
1007
val lastModified: Long
1008
}
1009
1010
// Form validation
1011
interface ValidityState {
1012
val valid: Boolean
1013
val badInput: Boolean
1014
val customError: Boolean
1015
val patternMismatch: Boolean
1016
val rangeOverflow: Boolean
1017
val rangeUnderflow: Boolean
1018
val stepMismatch: Boolean
1019
val tooLong: Boolean
1020
val tooShort: Boolean
1021
val typeMismatch: Boolean
1022
val valueMissing: Boolean
1023
}
1024
```