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) => void—Callback 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 ​

Use the component ref to trigger validation and read the current validation state and error message; see API Reference for member types and behavior.

API Reference ​

HardcoreInput — Props
NameTypeDefaultDescription
modelValue
string | undefined
undefined
The current input string, available through v-model for controlled binding.
Details

Declared type

string | undefined

Default value details

Resolved value
undefined

Source

HardcoreInput.vue:12

packages/ui/src/components/hardcore-input/HardcoreInput.vue

sound
boolean | undefined
true
Enables or disables typing sounds and success/failure sounds on validation-state transitions.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
true

Source

HardcoreInput.vue:13

packages/ui/src/components/hardcore-input/HardcoreInput.vue

rules
((val: string) => string | boolean)[] | undefined
[]

Factory evaluated per instance

String validation functions evaluated in order. Return true to pass, a string to fail with that message, or false to use the localized generic error. An empty array produces no validation error.
Details

Declared type

((val: string) => string | boolean)[] | undefined

Default value details

Resolved value
[]

Source

HardcoreInput.vue:14

packages/ui/src/components/hardcore-input/HardcoreInput.vue

shakeOnError
boolean | undefined
true
Enables the input shake feedback after validation fails.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
true

Source

HardcoreInput.vue:15

packages/ui/src/components/hardcore-input/HardcoreInput.vue

type
string | undefined
"text"
The HTML input type passed to the native input element.
Details

Declared type

string | undefined

Default value details

Resolved value
"text"

Source

HardcoreInput.vue:16

packages/ui/src/components/hardcore-input/HardcoreInput.vue

placeholder
string | undefined
undefined
The native placeholder text shown when the input is empty.
Details

Declared type

string | undefined

Default value details

Resolved value
undefined

Source

HardcoreInput.vue:17

packages/ui/src/components/hardcore-input/HardcoreInput.vue

disabled
boolean | undefined
false
Disables the native input element.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
false

Source

HardcoreInput.vue:18

packages/ui/src/components/hardcore-input/HardcoreInput.vue

readonly
boolean | undefined
false
Makes the native input element read-only.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
false

Source

HardcoreInput.vue:19

packages/ui/src/components/hardcore-input/HardcoreInput.vue

validateOn
"input" | "submit" | "blur" | undefined
"blur"
Selects when the component automatically validates. For submit, the component does not listen for form submission; call the exposed validate() method explicitly.
Details

Declared type

"input" | "submit" | "blur" | undefined

Literal values

  • "blur"
  • "input"
  • "submit"

Default value details

Resolved value
"blur"

Source

HardcoreInput.vue:20

packages/ui/src/components/hardcore-input/HardcoreInput.vue

class
string | undefined
undefined
Custom CSS classes appended to the outer container around the input and validation message.
Details

Declared type

string | undefined

Default value details

Resolved value
undefined

Source

HardcoreInput.vue:21

packages/ui/src/components/hardcore-input/HardcoreInput.vue

HardcoreInput — Events
NameSignature or typeDescription
@update:modelValue
[value: string]
Emits the input string on user input or when IME composition completes. Intermediate composition values are not emitted.
Details

Declared type

[value: string]

Source

HardcoreInput.vue

packages/ui/src/components/hardcore-input/HardcoreInput.vue

@validation-change
[state: "default" | "success" | "error", message?: string | undefined]
Emitted when the validation state changes or the error message changes while in the error state. Unchanged state/message pairs are not repeated; returning to default with empty rules is also reported.
  • When a rule returns false, message uses the hardcoreInput.invalidInput text “Invalid input”.
Details

Declared type

[state: "default" | "success" | "error", message?: string | undefined]

Literal values

  • "default"
  • "error"
  • "success"

Source

HardcoreInput.vue

packages/ui/src/components/hardcore-input/HardcoreInput.vue

HardcoreInput — Slots
NameSignature or typeDescription
#default
{}
Replaces the default validation-face area to the right of the input; the slot has no parameters.
Details

Declared type

{}

Source

HardcoreInput.vue

packages/ui/src/components/hardcore-input/HardcoreInput.vue

HardcoreInput — Exposes
NameSignature or typeDescription
validate
() => boolean
Validates the current modelValue and returns whether it passes. Within a Form context, the form value is synchronized first.
Details

Declared type

() => boolean

Source

HardcoreInput.vue:222

packages/ui/src/components/hardcore-input/HardcoreInput.vue

validationState
ValidationState
The validation state exposed on the component instance. Vue automatically unwraps its internal Ref, so read default, success, or error through hardcoreInputRef.value.validationState.
Details

Declared type

ValidationState

Literal values

  • "default"
  • "error"
  • "success"

Referenced types

ValidationState
Type definition — ValidationState
export type ValidationState = 'default' | 'success' | 'error'

Source

HardcoreInput.vue:223

packages/ui/src/components/hardcore-input/HardcoreInput.vue

errorMessage
string
The current validation error exposed on the component instance. Vue automatically unwraps its internal Ref; the value is an empty string when there is no error.
Details

Declared type

string

Source

HardcoreInput.vue:224

packages/ui/src/components/hardcore-input/HardcoreInput.vue

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.