Pattern Matching & Custom Messages

Use Schema.pattern(regex) for format validation. Chain with Schema.annotations({ message: () => '...' }) to replace the default technical error with a user-friendly message.

3-20 characters, letters, numbers, underscores

Lowercase, hyphens only


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 PatternSchema = pipe(
7		Schema.Struct({
8			username: pipe(
9				Schema.String,
10				Schema.minLength(3),
11				Schema.maxLength(20),
12				Schema.pattern(/^[a-zA-Z0-9_]+$/),
13				Schema.annotations({
14					message: () => "3-20 characters — letters, numbers, underscores only"
15				}),
16				withField({
17					label: "Username",
18					placeholder: "john_doe",
19					description: "3-20 characters, letters, numbers, underscores",
20					colSpan: 6
21				})
22			),
23			email: pipe(
24				Schema.String,
25				Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/),
26				Schema.annotations({ message: () => "Please enter a valid email address" }),
27				withField({ label: "Email", inputType: "email", colSpan: 6 })
28			),
29			zipCode: pipe(
30				Schema.String,
31				Schema.pattern(/^\d{5}(-\d{4})?$/),
32				Schema.annotations({
33					message: () => "Enter a valid US zip code (e.g., 90210 or 90210-1234)"
34				}),
35				withField({ label: "Zip Code", placeholder: "90210", colSpan: 6 })
36			),
37			slug: pipe(
38				Schema.String,
39				Schema.pattern(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
40				Schema.annotations({
41					message: () => "Slug must be lowercase letters, numbers, and hyphens only"
42				}),
43				withField({ label: "URL Slug", placeholder: "my-article-title", colSpan: 6 })
44			)
45		}),
46		withFormLayout({
47			columns: 12,
48			sections: [{ id: "main", title: "Pattern Validation" }]
49		})
50	);
51
52	const controller = new FormController(PatternSchema, { validateOnBlur: true });
53</script>
54
55<SchemaForm {controller} onSubmit={(d) => console.log(d)} submitText="Validate Fields" />

Password Strength Validation

Chain minLength, maxLength, and pattern to enforce complex password policies. Use a single annotations.message to give one clear, holistic error rather than multiple messages.

Min 8 chars with uppercase, lowercase, number, and special character


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 PasswordSchema = pipe(
7		Schema.Struct({
8			currentPassword: pipe(
9				Schema.String,
10				Schema.minLength(1),
11				Schema.annotations({ message: () => "Current password is required" }),
12				withField({ label: "Current Password", inputType: "password" })
13			),
14			newPassword: pipe(
15				Schema.String,
16				Schema.minLength(8),
17				Schema.maxLength(128),
18				// Requires: uppercase, lowercase, number, special character
19				Schema.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).+$/),
20				Schema.annotations({
21					message: () =>
22						"Must have uppercase, lowercase, number, and special character (@$!%*?&)"
23				}),
24				withField({
25					label: "New Password",
26					inputType: "password",
27					description: "Min 8 chars with uppercase, lowercase, number, and special character"
28				})
29			)
30		}),
31		withFormLayout({
32			columns: 1,
33			sections: [{ id: "main", title: "Password Strength Rules" }]
34		})
35	);
36
37	// validateOnChange: false prevents jittery errors while typing
38	const controller = new FormController(PasswordSchema, {
39		validateOnBlur: true,
40		validateOnChange: false
41	});
42</script>
43
44<SchemaForm {controller} onSubmit={(d) => console.log(d)} submitText="Update Password" />

Required vs Optional Checkboxes

RequiredCheckbox enforces the checked state with a default message. requiredCheckbox("Custom message") uses your own error text. Regular Schema.Boolean checkboxes are optional and never block submission.


Code Svelte
1
2<script lang="ts">
3	import { Schema, pipe } from "effect";
4	import {
5		SchemaForm, FormController, withField, withFormLayout,
6		RequiredCheckbox, requiredCheckbox
7	} from "@kareyes/aether/forms";
8
9	const CheckboxSchema = pipe(
10		Schema.Struct({
11			// Default error: "This field is required"
12			acceptTerms: pipe(
13				RequiredCheckbox,
14				withField({ label: "I accept the Terms of Service", inputType: "checkbox" })
15			),
16			// Custom error message
17			acceptPrivacy: pipe(
18				requiredCheckbox("You must accept the Privacy Policy to continue"),
19				withField({ label: "I accept the Privacy Policy", inputType: "checkbox" })
20			),
21			// Also custom error
22			ageConfirmation: pipe(
23				requiredCheckbox("You must confirm you are 18 years or older"),
24				withField({ label: "I confirm I am 18 years or older", inputType: "checkbox" })
25			),
26			// Plain Schema.Boolean — never blocks submission
27			marketingOptIn: pipe(
28				Schema.Boolean,
29				withField({ label: "Receive marketing emails (optional)", inputType: "checkbox" })
30			)
31		}),
32		withFormLayout({
33			columns: 1,
34			sections: [{ id: "main", title: "Checkbox Validation" }]
35		})
36	);
37
38	const controller = new FormController(CheckboxSchema, {
39		validateOnBlur: true,
40		initialValues: { marketingOptIn: false }
41	});
42</script>
43
44<SchemaForm {controller} onSubmit={(d) => console.log(d)} submitText="Create Account" />

String Length Constraints

Schema.minLength(n) and Schema.maxLength(n) set character bounds at the schema level. Use the description field in withField() to communicate these bounds inline below the input.

5 to 60 characters

20 to 160 characters — ideal for SEO meta descriptions

Optional label, max 30 characters


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 LengthSchema = pipe(
7		Schema.Struct({
8			title: pipe(
9				Schema.String,
10				Schema.minLength(5),
11				Schema.maxLength(60),
12				Schema.annotations({ message: () => "Title must be 5-60 characters" }),
13				withField({
14					label: "Post Title",
15					placeholder: "Enter a compelling title...",
16					description: "5 to 60 characters"
17				})
18			),
19			excerpt: pipe(
20				Schema.String,
21				Schema.minLength(20),
22				Schema.maxLength(160),
23				Schema.annotations({ message: () => "Excerpt must be 20-160 characters" }),
24				withField({
25					label: "Meta Excerpt",
26					inputType: "textarea",
27					placeholder: "Short summary for search engines...",
28					// Use description to surface constraints inline
29					description: "20 to 160 characters — ideal for SEO meta descriptions"
30				})
31			),
32			tag: pipe(
33				Schema.String,
34				Schema.minLength(2),
35				Schema.maxLength(30),
36				Schema.annotations({ message: () => "Tag must be 2-30 characters" }),
37				withField({ label: "Tag", placeholder: "svelte", description: "Max 30 characters" })
38			)
39		}),
40		withFormLayout({
41			columns: 1,
42			sections: [{ id: "main", title: "String Length Constraints" }]
43		})
44	);
45
46	const controller = new FormController(LengthSchema, { validateOnBlur: true });
47</script>
48
49<SchemaForm {controller} onSubmit={(d) => console.log(d)} submitText="Publish Post" />

Chained Validation Constraints

Multiple validators pipe together for each field. All rules must pass before the field is considered valid. A single Schema.annotations at the end of the chain sets one unified error message covering all the rules.

Must start with a letter

Must start with http:// or https://

8-character uppercase code


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	// All validators pipe together — all must pass before the field is valid.
7	// Schema.annotations at the end sets one unified error that covers all rules.
8	const SignupSchema = pipe(
9		Schema.Struct({
10			username: pipe(
11				Schema.String,
12				Schema.minLength(3),
13				Schema.maxLength(20),
14				Schema.pattern(/^[a-zA-Z][a-zA-Z0-9_]*$/),
15				Schema.annotations({
16					message: () =>
17						"Must start with a letter, 3-20 chars, letters/numbers/underscores"
18				}),
19				withField({ label: "Username", placeholder: "cooluser42", colSpan: 6 })
20			),
21			handle: pipe(
22				Schema.String,
23				Schema.minLength(1),
24				Schema.maxLength(15),
25				Schema.pattern(/^[a-zA-Z0-9_]+$/),
26				Schema.annotations({
27					message: () => "Handle: 1-15 chars, letters/numbers/underscores"
28				}),
29				withField({ label: "Twitter Handle", placeholder: "johndoe", colSpan: 6 })
30			),
31			website: pipe(
32				Schema.String,
33				Schema.pattern(/^https?:\/\/.+\..+/),
34				Schema.annotations({
35					message: () => "Enter a valid URL starting with http:// or https://"
36				}),
37				withField({ label: "Website", inputType: "url", placeholder: "https://mysite.com" })
38			),
39			inviteCode: pipe(
40				Schema.String,
41				Schema.minLength(8),
42				Schema.maxLength(8),
43				Schema.pattern(/^[A-Z0-9]{8}$/),
44				Schema.annotations({
45					message: () => "Invite code must be exactly 8 uppercase alphanumeric characters"
46				}),
47				withField({ label: "Invite Code", placeholder: "ABCD1234" })
48			)
49		}),
50		withFormLayout({
51			columns: 12,
52			sections: [{ id: "main", title: "Chained Constraints" }]
53		})
54	);
55
56	const controller = new FormController(SignupSchema, { validateOnBlur: true });
57</script>
58
59<SchemaForm {controller} onSubmit={(d) => console.log(d)} submitText="Create Account" />