pval

Validate component inputs at runtime with composable type and shape validators.

pvalApp and Components

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

Check a runtime value explicitlyLive API

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:

  1. opt-in
  2. local to the component
  3. runtime-only
  4. non-coercive
  5. 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:

  1. 'throw' (default): invalid props throw immediately.
  2. 'warn': invalid props are reported through warningHandler.warning(...).
  3. '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)}`)
  }
}
Validation checks rather than converts

Use validators for a runtime contract. Convert inputs deliberately when your application needs a different type.

See Also

  1. defineComponent
  2. Components guide
  3. TypeScript guide