Single Column Layout
Set columns: 1 in withFormLayout() for a stacked, full-width
form. Best for simple forms or narrow containers.
1
2<script lang="ts">
3 import { Schema, pipe } from "effect";
4 import { SchemaForm, FormController, withField, withFormLayout } from "@kareyes/aether/forms";
5
6 // columns: 1 stacks every field full-width, top to bottom
7 const ContactSchema = pipe(
8 Schema.Struct({
9 fullName: pipe(Schema.String, withField({ label: "Full Name", placeholder: "John Doe" })),
10 email: pipe(
11 Schema.String,
12 withField({ label: "Email", inputType: "email", placeholder: "you@example.com" })
13 ),
14 subject: pipe(Schema.String, withField({ label: "Subject" })),
15 message: pipe(
16 Schema.String,
17 withField({ label: "Message", inputType: "textarea" })
18 )
19 }),
20 withFormLayout({
21 columns: 1,
22 sections: [{ id: "main", title: "Contact Us" }]
23 })
24 );
25
26 const controller = new FormController(ContactSchema);
27</script>
28
29<SchemaForm {controller} onSubmit={(d) => console.log(d)} submitText="Send Message" />Two Column Layout
Set columns: 2 to arrange fields into two equal-width columns automatically.
Every field without an explicit colSpan takes half the width.
1
2<script lang="ts">
3 import { Schema, pipe } from "effect";
4 import { SchemaForm, FormController, withField, withFormLayout } from "@kareyes/aether/forms";
5
6 // columns: 2 places every field in a two-column grid automatically.
7 // All fields without an explicit colSpan take equal half-width.
8 const TwoColumnSchema = pipe(
9 Schema.Struct({
10 firstName: pipe(Schema.String, withField({ label: "First Name" , colSpan:1 })),
11 lastName: pipe(Schema.String, withField({ label: "Last Name", colSpan:1 })),
12 email: pipe(Schema.String, withField({ label: "Email", inputType: "email", colSpan:1 })),
13 phone: pipe(Schema.String, withField({ label: "Phone", inputType: "tel", mask: "phone", colSpan:1 }))
14 }),
15 withFormLayout({
16 columns: 2,
17 sections: [{ id: "main", title: "Two Column Layout" }]
18 })
19 );
20
21 const controller = new FormController(TwoColumnSchema);
22</script>
23
24<SchemaForm {controller} onSubmit={(d) => console.log(d)} submitText="Save" />12-Column Grid with colSpan
Use columns: 12 with per-field colSpan for pixel-precise layouts. colSpan: 4 = one-third, colSpan: 6 = half, colSpan: 12 = full width.
1
2<script lang="ts">
3 import { Schema, pipe } from "effect";
4 import { SchemaForm, FormController, withField, withFormLayout } from "@kareyes/aether/forms";
5
6 // columns: 12 with per-field colSpan for precise layout control:
7 // colSpan: 4 = one-third width
8 // colSpan: 6 = half width
9 // colSpan: 8 = two-thirds width
10 // colSpan: 12 = full width
11 const GridSchema = pipe(
12 Schema.Struct({
13 firstName: pipe(Schema.String, withField({ label: "First Name", colSpan: 4 })),
14 middleName: pipe(Schema.String, withField({ label: "Middle Name", colSpan: 4 })),
15 lastName: pipe(Schema.String, withField({ label: "Last Name", colSpan: 4 })),
16 email: pipe(Schema.String, withField({ label: "Email", inputType: "email", colSpan: 8 })),
17 zipCode: pipe(Schema.String, withField({ label: "Zip Code", colSpan: 4 })),
18 bio: pipe(
19 Schema.String,
20 withField({ label: "Biography", inputType: "textarea", colSpan: 12 })
21 )
22 }),
23 withFormLayout({
24 columns: 12,
25 sections: [{ id: "main", title: "12-Column Grid" }]
26 })
27 );
28
29 const controller = new FormController(GridSchema);
30</script>
31
32<SchemaForm {controller} onSubmit={(d) => console.log(d)} submitText="Submit" />Multi-Section — Default Variant
Define named sections in withFormLayout() and assign each field with section: "id". The order property controls rendering order.
No sectionVariant prop renders sections as simple labeled groups.
1
2<script lang="ts">
3 import { Schema, pipe } from "effect";
4 import { SchemaForm, FormController, withField, withFormLayout } from "@kareyes/aether/forms";
5
6 // Assign fields to sections via section: "id".
7 // Sections render as labeled dividers in the default variant.
8 // The order property controls which section appears first.
9 const AddressSchema = pipe(
10 Schema.Struct({
11 firstName: pipe(
12 Schema.String,
13 withField({ label: "First Name", section: "personal", colSpan: 6 })
14 ),
15 lastName: pipe(
16 Schema.String,
17 withField({ label: "Last Name", section: "personal", colSpan: 6 })
18 ),
19 dob: pipe(
20 Schema.String,
21 withField({ label: "Date of Birth", inputType: "date", section: "personal" })
22 ),
23 street: pipe(Schema.String, withField({ label: "Street Address", section: "address" })),
24 city: pipe(Schema.String, withField({ label: "City", section: "address", colSpan: 8 })),
25 zip: pipe(Schema.String, withField({ label: "Zip Code", section: "address", colSpan: 4 }))
26 }),
27 withFormLayout({
28 columns: 12,
29 sections: [
30 { id: "personal", title: "Personal Information", order: 1 },
31 { id: "address", title: "Mailing Address", order: 2 }
32 ]
33 })
34 );
35
36 const controller = new FormController(AddressSchema);
37</script>
38
39<!-- No sectionVariant prop = default labeled dividers -->
40<SchemaForm {controller} onSubmit={(d) => console.log(d)} submitText="Save Details" />Multi-Section — Card Variant
Pass sectionVariant="card" to wrap each section in its own card. Ideal for
visually separating logical groups like payment details and billing address.
1
2<script lang="ts">
3 import { Schema, pipe } from "effect";
4 import { SchemaForm, FormController, withField, withFormLayout } from "@kareyes/aether/forms";
5
6 const PaymentSchema = pipe(
7 Schema.Struct({
8 cardHolder: pipe(
9 Schema.String,
10 withField({ label: "Name on Card", section: "card", colSpan: 12 })
11 ),
12 cardNumber: pipe(
13 Schema.String,
14 withField({ label: "Card Number", section: "card", colSpan: 8 })
15 ),
16 cvv: pipe(
17 Schema.String,
18 withField({ label: "CVV", section: "card", colSpan: 4 })
19 ),
20 billingName: pipe(
21 Schema.String,
22 withField({ label: "Full Name", section: "billing", colSpan: 12 })
23 ),
24 billingAddress: pipe(
25 Schema.String,
26 withField({ label: "Address", section: "billing", colSpan: 8 })
27 ),
28 billingZip: pipe(
29 Schema.String,
30 withField({ label: "Zip", section: "billing", colSpan: 4 })
31 )
32 }),
33 withFormLayout({
34 columns: 12,
35 sections: [
36 { id: "card", title: "Card Details", order: 1 },
37 { id: "billing", title: "Billing Address", order: 2 }
38 ]
39 })
40 );
41
42 const controller = new FormController(PaymentSchema);
43</script>
44
45<!-- sectionVariant="card" wraps each section in its own card -->
46<SchemaForm
47 {controller}
48 onSubmit={(d) => console.log(d)}
49 sectionVariant="card"
50 submitText="Pay Now"
51/>Multi-Section — Collapsible Variant
Pass sectionVariant="collapsible" for accordion-style sections. Great for
settings pages where users only need to expand the section they're editing.
1
2<script lang="ts">
3 import { Schema, pipe } from "effect";
4 import { SchemaForm, FormController, withField, withFormLayout } from "@kareyes/aether/forms";
5
6 const SettingsSchema = pipe(
7 Schema.Struct({
8 displayName: pipe(Schema.String, withField({ label: "Display Name", section: "profile" })),
9 email: pipe(
10 Schema.String,
11 withField({ label: "Email", inputType: "email", section: "profile" })
12 ),
13 twitter: pipe(
14 Schema.String,
15 withField({ label: "Twitter", placeholder: "@username", section: "social" })
16 ),
17 github: pipe(
18 Schema.String,
19 withField({ label: "GitHub", placeholder: "username", section: "social" })
20 ),
21 language: pipe(
22 Schema.String,
23 withField({
24 label: "Language",
25 inputType: "select",
26 section: "preferences",
27 options: [
28 { value: "en", label: "English" },
29 { value: "es", label: "Spanish" },
30 { value: "fr", label: "French" }
31 ]
32 })
33 ),
34 emailNotifications: pipe(
35 Schema.Boolean,
36 withField({ label: "Email notifications", inputType: "switch", section: "preferences" })
37 )
38 }),
39 withFormLayout({
40 columns: 12,
41 sections: [
42 { id: "profile", title: "Profile", order: 1 },
43 { id: "social", title: "Social Links", order: 2 },
44 { id: "preferences", title: "Preferences", order: 3 }
45 ]
46 })
47 );
48
49 const controller = new FormController(SettingsSchema, {
50 initialValues: { emailNotifications: true }
51 });
52</script>
53
54<!-- sectionVariant="collapsible" makes sections accordion-style -->
55<SchemaForm
56 {controller}
57 onSubmit={(d) => console.log(d)}
58 sectionVariant="collapsible"
59 submitText="Save Settings"
60/>Responsive Column Spans
Combine colSpan (mobile-first base), colSpanMd (≥768 px), and colSpanLg (≥1024 px) on any field to make the grid reflow at different
breakpoints. Requires columns: 12 in withFormLayout(). Use colSpan: "full" as shorthand for 12 (always full width).
1
2<script lang="ts">
3 import { Schema, pipe } from "effect";
4 import { SchemaForm, FormController, withField, withFormLayout } from "@kareyes/aether/forms";
5
6 // Use columns: 12 and combine colSpan (mobile) with colSpanMd / colSpanLg
7 // for fields that should reflow at different breakpoints.
8 //
9 // colSpan → applied on all screen sizes (mobile-first default)
10 // colSpanMd → overrides at the md breakpoint (≥768 px)
11 // colSpanLg → overrides at the lg breakpoint (≥1024 px)
12 //
13 // colSpan: "full" is shorthand for colSpan: 12 (always full width).
14 const ProfileSchema = pipe(
15 Schema.Struct({
16 // Full width on mobile, half on md, one-third on lg
17 firstName: pipe(
18 Schema.String,
19 withField({ label: "First Name", colSpan: "full", colSpanMd: 6, colSpanLg: 4 })
20 ),
21 lastName: pipe(
22 Schema.String,
23 withField({ label: "Last Name", colSpan: "full", colSpanMd: 6, colSpanLg: 4 })
24 ),
25 // Full width on mobile & md, two-thirds on lg
26 email: pipe(
27 Schema.String,
28 withField({ label: "Email", inputType: "email", colSpan: "full", colSpanMd: "full", colSpanLg: 8 })
29 ),
30 // Full width on mobile, one-third on lg
31 phone: pipe(
32 Schema.String,
33 withField({ label: "Phone", inputType: "tel", mask: "phone", colSpan: "full", colSpanLg: 4 })
34 ),
35 // Always full width regardless of screen size
36 bio: pipe(
37 Schema.String,
38 withField({ label: "Bio", inputType: "textarea", colSpan: "full" })
39 )
40 }),
41 withFormLayout({
42 columns: 12,
43 sections: [{ id: "main", title: "Edit Profile" }]
44 })
45 );
46
47 const controller = new FormController(ProfileSchema);
48</script>
49
50<SchemaForm {controller} onSubmit={(d) => console.log(d)} submitText="Save Profile" />