pval
Validate component inputs at runtime with composable type and shape validators.
Validate component inputs at runtime with composable type and shape validators.
Overview
pval is the built-in prop-validator namespace for head.validateProps(...).
It groups Regor's built-in runtime validators under one object so component code stays compact and autocomplete-friendly:
import { pval } from 'regor' head.validateProps({
title: pval.isString,
count: pval.optional(pval.isNumber),
tags: pval.arrayOf(pval.isString),
}) Try it live
Validate the candidate to see its runtime contract.
Validators check runtime types. They do not coerce a string into a number.
import { defineComponent, html, pval, ref } from 'regor'
export function createValidation(interactive = false) {
const ValidatorField = defineComponent(
html` <div class="guide-demo">
<div class="guide-controls">
<label for="validation-value">Candidate value</label
><input
id="validation-value"
type="text"
maxlength="20"
r-model="text"
:value="text"
:disabled="!interactive"
/><label class="guide-check"
><input
type="checkbox"
r-model="asNumber"
:checked="asNumber"
:disabled="!interactive"
/>
Convert to Number before validating</label
><button type="button" @click="validate" :disabled="!interactive">
Validate with pval.isNumber
</button>
</div>
<p class="guide-event" role="status">{{ result }}</p>
<p class="guide-hint">
Validators check runtime types. They do not coerce a string into a
number.
</p>
</div>`,
{
context: (head) => {
const text = ref('12'),
asNumber = ref(true),
result = ref('Validate the candidate to see its runtime contract.')
const validate = () => {
try {
pval.isNumber(asNumber() ? Number(text()) : text(), 'count', head)
result('Valid: count is a number.')
} catch (error) {
result(error instanceof Error ? error.message : String(error))
}
}
return { interactive, text, asNumber, result, validate }
},
},
)
return { interactive, components: { ValidatorField } }
}
export const validationTemplate = html`<ValidatorField />` import { createApp, RegorConfig, useScope } from 'regor'
import { apiExamples } from './examples'
const element = document.getElementById('api-demo')
const name = element?.dataset.example
if (element && name && Object.hasOwn(apiExamples, name)) {
const example = apiExamples[name as keyof typeof apiExamples]
const scope = useScope<object>(() => example.create(true))
const config =
'config' in scope.context && scope.context.config instanceof RegorConfig
? scope.context.config
: undefined
createApp(scope, { element, template: example.template }, config)
} Validate a number or keep the candidate as a string. Failed validation is caught and displayed in the preview.
When to use it
Use pval inside defineComponent(..., { context(head) { ... } }) when you want an explicit runtime contract for component inputs.
Validation is:
- opt-in
- local to the component
- runtime-only
- non-coercive
- controlled by
RegorConfig.propValidationMode
Validation mode
Runtime prop-validation behavior is controlled through RegorConfig.propValidationMode:
import { RegorConfig } from 'regor'
const config = new RegorConfig()
config.propValidationMode = 'warn' Modes:
'throw'(default): invalid props throw immediately.'warn': invalid props are reported throughwarningHandler.warning(...).'off': runtime prop validation is skipped.
Built-in validators
pval.isString
Ensures a prop is a string.
head.validateProps({
title: pval.isString,
}) pval.isNumber
Ensures a prop is a number.
head.validateProps({
count: pval.isNumber,
}) pval.isBoolean
Ensures a prop is a boolean.
head.validateProps({
disabled: pval.isBoolean,
}) pval.isClass(SomeClass)
Ensures a prop is an instance of a runtime class.
head.validateProps({
editor: pval.isClass(HostEditorTab),
}) This works only with runtime classes, not interfaces or type aliases.
pval.optional(validator)
Allows undefined. Otherwise delegates to the wrapped validator.
head.validateProps({
count: pval.optional(pval.isNumber),
}) pval.nullable(validator)
Allows null. Otherwise delegates to the wrapped validator.
head.validateProps({
count: pval.nullable(pval.isNumber),
}) pval.or(...validators)
Accepts the value when any of the provided validators succeeds.
This is useful for union-style runtime contracts:
head.validateProps({
value: pval.or(pval.isString, pval.isNumber),
}) It also works well with ref-aware unions:
head.validateProps({
value: pval.or(pval.isString, pval.refOf(pval.isString)),
}) Example failure:
Invalid prop "value": expected string or expected ref<string>, got ref<number>(1). pval.oneOf(values)
Ensures the value is one of the provided literals.
head.validateProps({
mode: pval.oneOf(['create', 'edit'] as const),
}) pval.arrayOf(validator)
Ensures the value is an array and validates each entry.
head.validateProps({
tags: pval.arrayOf(pval.isString),
}) Nested errors include the failing index:
Invalid prop "tags[1]": expected string, got number (2). pval.shape({ ... })
Ensures the value is an object and validates selected nested keys.
head.validateProps({
meta: pval.shape({
slug: pval.isString,
retries: pval.nullable(pval.isNumber),
}),
}) Nested errors include the failing path:
Invalid prop "meta.slug": expected string, got number (2). pval.refOf(validator)
Ensures the prop is a Regor ref and validates its current value using the wrapped validator.
Use this for dynamic single-prop bindings such as :title="titleRef":
head.validateProps({
title: pval.refOf(pval.isString),
}) pval.describe(value)
Returns the same got ... description used by Regor's built-in validators.
This is useful in custom validators when you want your own error details to match the built-in validator style, including explicit Regor ref handling.
const isPositiveNumber: PropValidator<number> = (value, name) => {
if (typeof value !== 'number' || value <= 0) {
pval.fail(name, `expected positive number, ${pval.describe(value)}`)
}
} pval.fail(name, detail)
Throws the same structured validation failure used by Regor's built-in validators.
This is the recommended way for custom validators to report invalid input, because it preserves nested prop paths and lets head.validateProps(...) format the final component-aware error consistently.
const isNonEmptyString: PropValidator<string> = (value, name) => {
if (typeof value !== 'string' || value.trim() === '') {
pval.fail(name, `expected non-empty string, ${pval.describe(value)}`)
}
} Dynamic props vs object props
Single-prop bindings like :title="titleRef" may arrive as refs at runtime, so pval.refOf(...) is appropriate there.
Object-style :context="{ meta: { slug: 'x' } }" values can be validated directly with pval.shape(...).
Custom validators
You are not limited to pval. Users can write any validator that matches PropValidator<T>:
import { pval, type PropValidator } from 'regor'
const isNonEmptyString: PropValidator<string> = (value, name) => {
if (typeof value !== 'string' || value.trim() === '') {
pval.fail(name, `expected non-empty string, ${pval.describe(value)}`)
}
} Custom validators also receive head as the third argument:
const startsWithPrefix: PropValidator<string> = (value, name, head) => {
const services = head.requireContext(AppServices)
if (typeof value !== 'string' || !value.startsWith(services.prefix)) {
pval.fail(name, `expected prefixed value, ${pval.describe(value)}`)
}
} Use validators for a runtime contract. Convert inputs deliberately when your application needs a different type.