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.
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.
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.
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.
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.
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" />