Single Column Layout

Set columns: 1 in withFormLayout() for a stacked, full-width form. Best for simple forms or narrow containers.


Code Svelte
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.


Code Svelte
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.


Code Svelte
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.

Personal Information

Mailing Address


Code Svelte
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.

Card Details
Billing Address

Code Svelte
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.


Code Svelte
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).

colSpan: "full" → full width on all screens (mobile default)
colSpanMd: 6   → half width at md (≥768 px)
colSpanLg: 4   → one-third width at lg (≥1024 px)

Code Svelte
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" />