or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

css-styling.mdevent-handling.mdform-controls.mdhtml-attributes.mdhtml-elements.mdindex.md

form-controls.mddocs/

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

```