Skip to content

Hardcore Input

The ultimate form input validator. Synthesizes 8-bit retro electronic sound effects on the fly via Web Audio API (zero audio dependencies), while triggering neobrutalist emoji bounces and physical shake on validation state changes.

Demo

Preview

基础用法(Blur 校验)

多规则校验(Input 实时校验)

禁用音效

关闭抖动 (shakeOnError=false)

只读状态 (readonly)

Installation

pnpm dlx brutx-vue@latest add hardcore-input

Usage

vue
<script setup>
import { ref } from 'vue'
import { HardcoreInput } from 'brutx-ui-vue'

const value = ref('')

const minLength = (val) => val.length >= 5 || 'At least 5 characters required'
const hasNumber = (val) => /\d/.test(val) || 'Must contain a number'
</script>

<template>
    <HardcoreInput
        v-model="value"
        placeholder="Enter content..."
        :rules="[minLength, hasNumber]"
        validate-on="blur"
    />
</template>

Validation Trigger Modes

ModeDescription
blurValidate on blur (default)
inputValidate in real-time on every input
submitValidate when validate() is called externally

Sound System

Uses Web Audio API via the useAudioEngine composable to synthesize three sound effects:

SoundWaveformDescription
typeTriangleTyping sound with 50ms throttle
successSineValidation passed, rising frequency
failSquareValidation failed, falling frequency

Sound can be disabled via the sound prop.

Visual Feedback

  • Success: Input border changes to --brutal-success color, cool emoji SVG appears on the right
  • Error: Input border changes to --brutal-destructive color, angry emoji SVG appears on the right, input shakes with animation
  • Default: No additional feedback

Form Integration

HardcoreInput integrates with the Form system. When placed inside a FormField, it automatically syncs validation state:

vue
<Form v-slot="{ handleSubmit }">
    <FormField v-slot="{ componentField }" name="username">
        <FormItem>
            <FormLabel>Username</FormLabel>
            <HardcoreInput v-bind="componentField" :rules="[minLength]" />
            <FormMessage />
        </FormItem>
    </FormField>
</Form>

Composables

useFormFieldValidation

The internal validation state machine of HardcoreInput has been extracted as a standalone useFormFieldValidation composable. It can be reused in any custom form control to share the same validation logic (rule evaluation, state machine transitions, error message management, trigger timing control). It does not depend on vee-validate and is suitable for scenarios where the Form system cannot be used or lightweight inline validation is needed.

ts
import { useFormFieldValidation } from 'brutx-ui-vue'
import type { UseFormFieldValidationOptions, ValidationRule, ValidateOn } from 'brutx-ui-vue'

// Validation rules: return true for pass, return string for error message
const minLength: ValidationRule<string> = (val) => val.length >= 5 || 'At least 5 characters required'
const hasNumber: ValidationRule<string> = (val) => /\d/.test(val) || 'Must contain a number'

const {
    validationState,        // Current validation state
    errorMessage,           // Current error message
    validate,               // Trigger validation manually
    reset,                  // Reset to default state
    shouldValidateOnInput,  // Whether to validate on input
    shouldValidateOnBlur,   // Whether to validate on blur
} = useFormFieldValidation({
    rules: [minLength, hasNumber],
    validateOn: 'blur',
    defaultErrorMessage: 'Validation failed',
    onValidationChange: (state, message) => {
        // State change callback, can be used for side effects like sound and shake
    },
})

UseFormFieldValidationOptions

PropTypeDefaultDescription
rulesMaybeRefOrGetter<ValidationRule<TValue>[]>[]Array of validation rules; when empty, validate always returns true and sets state to default
validateOnMaybeRefOrGetter<'input' | 'blur' | 'submit'>'submit'Validation trigger timing (only affects shouldValidateOnInput / shouldValidateOnBlur determination; the caller must invoke validate in the corresponding event)
defaultErrorMessageMaybeRefOrGetter<string>'Invalid value'Default error message when a rule returns false
onValidationChange(state: ValidationState, message?: string) => voidCallback on validation state change (only triggered when state actually changes, avoiding duplicates)

Return Values

PropertyTypeDescription
validationStateRef<'default' | 'success' | 'error'>Current validation state (reactive)
errorMessageRef<string>Current error message (reactive, empty string when no error)
validate(value)(value: TValue) => booleanValidates the given value; returns true on pass, false on failure and writes to errorMessage
reset()() => voidResets to default state and clears error message
shouldValidateOnInput()() => booleanWhether configured to validate on input
shouldValidateOnBlur()() => booleanWhether configured to validate on blur

ValidationRule

ts
type ValidationRule<TValue> = (value: TValue) => boolean | string

A rule function receives the current value. Return true to pass, return string to fail with that string as the error message, return false to fail using defaultErrorMessage.

Usage Example

Reuse the same validation logic in a custom input control:

vue
<script setup lang="ts">
import { ref } from 'vue'
import { useFormFieldValidation } from 'brutx-ui-vue'

const value = ref('')
const { validationState, errorMessage, validate, shouldValidateOnBlur } = useFormFieldValidation({
    rules: [(v) => v.length >= 3 || 'At least 3 characters'],
    validateOn: 'blur',
})

function onBlur() {
    if (shouldValidateOnBlur()) validate(value.value)
}
</script>

<template>
    <input
        v-model="value"
        @blur="onBlur"
        :aria-invalid="validationState === 'error'"
        :class="{
            'border-brutal-success': validationState === 'success',
            'border-brutal-destructive': validationState === 'error',
        }"
    />
    <p v-if="errorMessage" class="text-brutal-destructive text-sm">{{ errorMessage }}</p>
</template>

Note: useFormFieldValidation is not bound to a specific control. All timing checks (shouldValidateOnInput / shouldValidateOnBlur) only return boolean values, and the caller decides in which event to invoke validate. For the submit timing, the caller must explicitly invoke validate externally (e.g. on form submission).

Programmatic Control

NameDescription
validate()Trigger validation manually
validationStateCurrent validation state
errorMessageCurrent error message

Props

PropTypeDefaultDescription
modelValuestringundefinedv-model binding value
soundbooleantrueWhether to enable 8-bit retro sound effects
rulesArray<(val: string) => boolean | string>[]List of validation rule functions
shakeOnErrorbooleantrueWhether to trigger input shake on error
typestring'text'HTML input type attribute
placeholderstringundefinedPlaceholder text
disabledbooleanfalseWhether disabled
readonlybooleanfalseWhether readonly
validateOn'input' | 'blur' | 'submit''blur'Validation trigger timing
classstringundefinedExternal class override

Events

EventPayloadDescription
@update:modelValue(value: string)v-model update event
@validation-change(state: ValidationState, message?: string)Validation state change event, only triggered when state actually changes

Slots

SlotScopeDescription
defaultAdditional content to the right of the input (e.g. icons, emoji area overlay)

Accessibility

  • Input has :aria-invalid and :aria-describedby set
  • Error message has aria-live="polite"
  • Shake and bounce animations are disabled when user prefers prefers-reduced-motion: reduce

Brute force builds.