Skip to content

Validation ​

Ooko provides synchronous, typed validation for reactive form models without owning or mutating their values.

Typed model rules ​

Pass a reactive object and a partial rule map to useOokoValidation. Each rule receives the value associated with its key, and rule() only accepts keys configured in that map.

ts
import { reactive } from "vue";

import { OokoValidator, useOokoValidation } from "ooko-ui/validation";

const user = reactive({
  age: undefined as number | undefined,
  city: "",
  name: "",
});

const validator = useOokoValidation(user, {
  age: (age) => ({
    valid: age === undefined || age >= 18,
    level: "error",
    message: "You must be an adult.",
  }),
  name: [OokoValidator.required(), OokoValidator.minLength(3)],
});

A rule returns an OokoValidationResult:

ts
interface OokoValidationResult<TMeta = unknown> {
  valid: boolean;
  level: "info" | "warning" | "error";
  message: string;
  meta?: TMeta;
}

Failed error results make the validator invalid. Failed info and warning results remain visible feedback but do not block submission. Successful results are not displayed.

API reference ​

Creates typed synchronous validation bindings for a reactive value object.

ts
function useOokoValidation<TValues extends object, const TRules extends OokoValidationRules<TValues>>(
  values: TValues,
  rules: TRules,
): UseOokoValidationReturn<Extract<keyof TRules, string>>

Parameters ​

ParameterTypeRequiredDefaultDescription
valuesTValuesyesundefinedReactive object whose current field values are validated.
rulesTRulesyesundefinedRules keyed by fields in the value object.

Returns ​

MemberTypeDescription
rule<TKey extends Extract<keyof TRules, string>>(key: TKey) => OokoValidationBindingReturns the binding for one configured field key.
check() => booleanReveals every configured field and reports aggregate validity.
getResults() => readonly OokoValidationResult[]Returns all current validation results without revealing them.
isValid() => booleanReports aggregate validity without revealing feedback.
reset() => voidHides feedback for every configured field.
TypeDefinition
OokoValidationBindinginterface OokoValidationBinding { /** Failed results currently visible to the user. */ readonly visibleResults: ComputedRef<readonly OokoValidationResult[]>; /** Reveals feedback and reports whether blocking rules pass. */ check: () => boolean; /** Reports whether blocking rules pass without revealing feedback. */ isValid: () => boolean; /** Hides previously revealed feedback. */ reset: () => void; }
OokoValidationControllerinterface OokoValidationController { /** Reveals every configured field and reports aggregate validity. */ check: () => boolean; /** Returns all current validation results without revealing them. */ getResults: () => readonly OokoValidationResult[]; /** Reports aggregate validity without revealing feedback. */ isValid: () => boolean; /** Hides feedback for every configured field. */ reset: () => void; [ookoValidationResolverKey]: (name: string) => OokoValidationBinding | undefined; }
OokoValidationLeveltype OokoValidationLevel = "error" | "info" | "warning";
OokoValidationResultinterface OokoValidationResult<TMeta = unknown> { valid: boolean; level: OokoValidationLevel; message: string; meta?: TMeta; }
OokoValidationRuletype OokoValidationRule<TValue, TMeta = unknown> = (value: TValue) => OokoValidationResult<TMeta>;
OokoValidationRulestype OokoValidationRules<TValues extends object> = Partial<{ [TKey in Extract<keyof TValues, string>]: OokoValidationRule<TValues[TKey]> | readonly OokoValidationRule<TValues[TKey]>[]; }>;
OptionalNumbertype OptionalNumber = number | null | undefined;
OptionalTexttype OptionalText = string | null | undefined;
UseOokoValidationReturninterface UseOokoValidationReturn<TRuleKey extends string> extends OokoValidationController { /** Returns the binding for one configured field key. */ rule: <TKey extends TRuleKey>(key: TKey) => OokoValidationBinding; }
ExportTypeDefault messageDescription
OokoValidator.accepted{ (message?: string): OokoValidationRule<boolean | null | undefined>; (message: string | undefined, value: boolean | null | undefined): OokoValidationResult; }"This field must be accepted."Requires a value to be strictly true.
OokoValidator.email{ (message?: string): OokoValidationRule<OptionalText>; (message: string | undefined, value: OptionalText): OokoValidationResult; }"Enter a valid email address."Requires a string to match the HTML email-address syntax.
OokoValidator.integer{ (message?: string): OokoValidationRule<OptionalNumber>; (message: string | undefined, value: OptionalNumber): OokoValidationResult; }"This field must be an integer."Requires an integer number.
OokoValidator.max{ (maximum: number, message?: string): OokoValidationRule<OptionalNumber>; (maximum: number, message: string | undefined, value: OptionalNumber): OokoValidationResult; }This field must be at most ${maximum}.Requires a finite number less than or equal to the maximum.
OokoValidator.maxLength{ (maximum: number, message?: string): OokoValidationRule<OptionalText>; (maximum: number, message: string | undefined, value: OptionalText): OokoValidationResult; }This field must contain at most ${maximum} characters.Requires a string to contain at most the given number of UTF-16 code units.
OokoValidator.min{ (minimum: number, message?: string): OokoValidationRule<OptionalNumber>; (minimum: number, message: string | undefined, value: OptionalNumber): OokoValidationResult; }This field must be at least ${minimum}.Requires a finite number greater than or equal to the minimum.
OokoValidator.minLength{ (minimum: number, message?: string): OokoValidationRule<OptionalText>; (minimum: number, message: string | undefined, value: OptionalText): OokoValidationResult; }This field must contain at least ${minimum} characters.Requires a string to contain at least the given number of UTF-16 code units.
OokoValidator.oneOf{ <T>(allowed: readonly T[], message?: string): OokoValidationRule<(T extends string ? string : T extends number ? number : T extends boolean ? boolean : T) | null | undefined>; <T>(allowed: readonly T[], message: string | undefined, value: NoInfer<(T extends string ? string : T extends number ? number : T extends boolean ? boolean : T) | null | undefined>): OokoValidationResult; }"Choose one of the allowed values."Requires strict equality with one of the allowed values.
OokoValidator.pattern{ (expression: RegExp, message?: string): OokoValidationRule<OptionalText>; (expression: RegExp, message: string | undefined, value: OptionalText): OokoValidationResult; }"This field does not match the expected format."Requires a string to match the supplied regular expression.
OokoValidator.required{ (message?: string): OokoValidationRule<unknown>; (message: string | undefined, value: unknown): OokoValidationResult; }"This field is required."Requires a non-empty value and rejects whitespace-only strings.
OokoValidator.sameAs{ <T>(expected: T | (() => T), message?: string): OokoValidationRule<(T extends string ? string : T extends number ? number : T extends boolean ? boolean : T) | null | undefined>; <T>(expected: T | (() => T), message: string | undefined, value: NoInfer<(T extends string ? string : T extends number ? number : T extends boolean ? boolean : T) | null | undefined>): OokoValidationResult; }"This field must match the expected value."Requires strict equality with a value or the current result of a getter.
OokoValidator.url{ (message?: string): OokoValidationRule<OptionalText>; (message: string | undefined, value: OptionalText): OokoValidationResult; }"Enter a valid HTTP or HTTPS URL."Requires an absolute HTTP or HTTPS URL.

Built-in rule behavior ​

All built-in rules return an error-level result. Every helper accepts a custom message.

All helpers can evaluate a standalone value by appending it after the message argument. Pass undefined as the message to use the default; explicitly passing an undefined value still runs the standalone check rather than returning a builder.

ts
const requiredRule = OokoValidator.required("Enter a name.");
const ruleResult = requiredRule(user.name);

const standaloneRequired = OokoValidator.required("Enter a name.", user.name);
const standaloneLength = OokoValidator.minLength(3, "Keep typing.", user.name);
const standaloneEmail = OokoValidator.email(undefined, "person@example.com");
const standaloneAge = OokoValidator.min(18, undefined, user.age);
const standaloneAcceptance = OokoValidator.accepted(undefined, false);

Presence, formats, and comparisons ​

  • required rejects null, undefined, empty strings, and whitespace-only strings. It still accepts 0 and false; use accepted when the value must be strictly true.
  • Every other rule except accepted accepts null, undefined, and empty strings where its value type permits them. Combine with required to enforce presence. Whitespace-only strings are not empty, and values are never trimmed or coerced.
  • Length rules use JavaScript string length (UTF-16 code units), as the existing minLength contract does. Minimum and maximum bounds are inclusive.
  • Numeric rules accept numbers only. min and max reject NaN and infinities; integer additionally rejects fractional numbers.
  • email checks one address using the HTML email syntax, including local domains such as person@localhost. It does not check mailbox existence or support display names and comma-separated address lists.
  • url parses an absolute HTTP(S) URL without DOM access. Relative URLs, other protocols, whitespace, and backslashes fail. URL validation is not a destination allowlist.
  • pattern preserves the supplied regular expression's flags and matching semantics. Use anchors when the entire value must match. Repeated checks with g or y are deterministic and do not modify the original expression's lastIndex.
  • oneOf accepts readonly lists and uses strict equality without coercion. An empty list rejects every non-empty value. sameAs also uses strict equality; objects are compared by reference.
  • Pass a getter to sameAs to track another reactive field. Passing a value captures that value when the builder is created. Functions are interpreted as getters.
ts
const credentials = reactive({ password: "", confirmation: "", role: "" });
const credentialsValidator = useOokoValidation(credentials, {
  confirmation: [OokoValidator.required(), OokoValidator.sameAs(() => credentials.password, "Passwords must match.")],
  role: OokoValidator.oneOf(["admin", "member"] as const),
});

Changing credentials.password re-evaluates the confirmation rule even if confirmation itself has not changed. Feedback stays hidden until blur or check(), as for other rules.

Field and form integration ​

Pass a typed binding directly to a control:

vue
<OInput v-model="user.name" label="Name" name="name" :validation="validator.rule('name')" />

Alternatively, pass the controller to OForm. OInput and OTextarea resolve their binding from their name. A direct field binding takes precedence over the form binding.

vue
<OForm :validation="validator">
  <OInput v-model="user.name" label="Name" name="name" />
</OForm>

Custom feedback is first revealed on blur, form submission, or validator.check(), then updates as the reactive model changes. Controlled error feedback takes presentation precedence over custom and native feedback.

State ownership and SSR ​

Each composable call owns independent state. It performs no browser work, persistence, or external synchronization and is deterministic during SSR and hydration.

Source organization ​

The public imports remain ooko-ui and ooko-ui/validation. Implementation modules are private and separated by responsibility:

  • src/composables/useOokoValidation.ts: reactive controller and per-field visibility.
  • src/validation/OokoValidation.types.ts: public validation types.
  • src/validation/OokoValidator.ts: the built-in rule catalog.
  • src/validation/rules/: presence, text, format, number, and comparison rules, with colocated tests.
  • src/validation/context.ts: private form/controller protocol.

Each rule family is small enough to read without region markers. The rule catalog has no Vue runtime dependency, and importing the composable alone does not include the built-in rules.