Skip to content
Brok UI

Loading…

No results

Masked Input

Open source

Masked credential field with reveal and copy actions, built on the input-group primitive.

Version
v2.0.5
Stability
stable
License
MIT
Related
Input Copy
Input
Currency Input

Preview

previews.components.masked-input.default.blade.php Blade
<x-ui.masked-input value="sk-live-9f2c4ade71b0demo" label="API key" class="mx-auto w-full max-w-md" />

Installation

terminal
php artisan ui:add masked-input

Note

This component ships an Alpine behavior module at resources/js/ui/masked-input.js. Import it once from your bundle so it registers on alpine:init:

resources/js/ui/index.js JS
import './masked-input.js';

Registry contract

php artisan ui:add masked-input writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.

  • blade resources/views/components/ui/masked-input.blade.php
  • js resources/js/ui/masked-input.js
Registry dependencies
input-group label
Packages
composer: jml/brok:^0.2
npm: alpinejs

Use with AI

A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.

masked-input.md
# Brok UI: Masked Input (`masked-input`)

Masked credential field with reveal and copy actions, built on the input-group primitive.

Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.

## Install

```bash
php artisan ui:add masked-input
```

## Usage

```blade
<x-ui.masked-input value="sk-live-9f2c4ade71b0demo" label="API key" class="mx-auto w-full max-w-md" />
```

## Props

- `value` (string, default `tok_a1b2c3d4e5f6g7h8`) — The secret value shown once revealed and written to the clipboard when copied.
- `label` (string, default `Secret`) — Visible label shown above the field.

## Use when

- Use when formatting, validation, or constrained entry meaningfully reduces errors, such as OTP, phone, currency, password, or numeric entry.
- Displaying a sensitive read-only value, such as an API key or token, hidden by default with an explicit reveal action.
- Pairing that value with a one-click copy action without ever showing it in plain text unintentionally.

## Avoid when

- Do not add specialized behavior if a plain field would be simpler and more forgiving.
- The value must be editable; compose input-group directly instead.
- The value is not sensitive and can be shown by default; use input-copy instead.

## Anti-patterns

- Adding formatting behavior that makes correction harder

## Rules

- Use the `<brok:masked-input>` tag (or `<x-ui.masked-input>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.

## Links

- Docs: https://brokui.dev/docs/components/masked-input
- Registry JSON (files, props, contract): https://brokui.dev/r/open/masked-input.json

Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
Prop Type Default Description
value string tok_a1b2c3d4e5f6g7h8 The secret value shown once revealed and written to the clipboard when copied.
label string Secret Visible label shown above the field.

Slots

  • default — Unused; the field renders from props only.

Data slots

Stable hooks for CSS overrides and browser tests.

masked-input

Behavior

  • Renders read-only over input-group; the field type toggles between password and text via the eye button.
  • The eye button reflects its state with aria-pressed; the copy button writes the value to the clipboard and briefly swaps its icon and label to a copied state.
  • The value lives in one Alpine state, so the read-only field and clipboard copy share a single source of truth.
  • Installs a JavaScript behavior module when the registry item includes resources/js/ui files.
  • Declares registry capability flags: a11y, interactive, responsive, rtl, darkMode, localized.

Guidance

Specialized input

Collect constrained information with error-reducing formatting.

Use when

  • Use when formatting, validation, or constrained entry meaningfully reduces errors, such as OTP, phone, currency, password, or numeric entry.
  • Displaying a sensitive read-only value, such as an API key or token, hidden by default with an explicit reveal action.
  • Pairing that value with a one-click copy action without ever showing it in plain text unintentionally.

Avoid when

  • Do not add specialized behavior if a plain field would be simpler and more forgiving.
  • The value must be editable; compose input-group directly instead.
  • The value is not sensitive and can be shown by default; use input-copy instead.

Use instead

  • Plain text or number field with validation

Anti-patterns

  • Adding formatting behavior that makes correction harder
Anatomy
root
Theming hooks
masked-input

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
Keyboard
managed
Focus
none
  • The reveal and copy buttons each carry an aria-label that changes with state ("Show value"/"Hide value", "Copy value"/"Copied"), never colour alone.
  • The field is announced via aria-label from the label prop even though it is read-only.
  • Semantic HTML and a stable data-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, so keyboard focus is always visible.
  • Disabled and invalid states are conveyed to assistive tech, not by color alone.
  • Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
  • Labels go through __() and layout uses logical properties (ms-*, text-start), so it mirrors under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Needs wire:key

Add a stable wire:key when Livewire can reorder this interactive component.

livewire-component.blade.php Blade
<div wire:key="masked-input-{{ $record->id }}">
    <x-ui.masked-input value="sk-live-9f2c4ade71b0demo" label="API key" class="mx-auto w-full max-w-md" />
</div>

Source

The exact, editable files ui:add writes into your app. Previews render this same code; there are no preview-only components.

resources/views/components/ui/masked-input.blade.php Blade
@props([
    'value' => 'tok_a1b2c3d4e5f6g7h8',
    'label' => 'Secret',
])

@php
    $config = ['value' => (string) $value];
@endphp

{{--
    Masked input — a masked credential field over <x-ui.input-group>. The input
    type toggles between password and text via `revealed`; the eye button
    reflects state with aria-pressed; the copy button writes the value to the
    clipboard and flips a transient `copied` flag that swaps the icon and label.
    The value lives in Alpine state so the read-only field and clipboard copy
    share one source of truth.
--}}
<div
    data-slot="masked-input"
    x-data="uiMaskedInput(@js($config))"
    {{ $attributes->merge(['class' => 'flex flex-col gap-2 text-foreground']) }}
>
    <x-ui.label :for="false" x-bind:for="$id('masked-input-field')">{{ __($label) }}</x-ui.label>

    <x-ui.input-group>
        <x-ui.input-group.input
            x-bind:id="$id('masked-input-field')"
            x-bind:type="revealed ? 'text' : 'password'"
            x-model="value"
            readonly
            autocomplete="off"
            spellcheck="false"
            :aria-label="__($label)"
        />

        <x-ui.input-group.button
            x-on:click="toggle()"
            x-bind:aria-pressed="revealed.toString()"
            x-bind:aria-label="revealed ? '{{ __('Hide value') }}' : '{{ __('Show value') }}'"
        >
            {{-- Eye (hidden state). --}}
            <svg x-show="!revealed" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                <path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z" />
                <circle cx="12" cy="12" r="3" />
            </svg>
            {{-- Eye-off (revealed state). --}}
            <svg x-show="revealed" x-cloak aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                <path d="M9.9 4.2A10.6 10.6 0 0 1 12 4c6.5 0 10 7 10 7a17.6 17.6 0 0 1-3.3 4.2M6.6 6.6A17.6 17.6 0 0 0 2 11s3.5 7 10 7a10.6 10.6 0 0 0 4.1-.8" />
                <path d="M9.9 9.9a3 3 0 0 0 4.2 4.2" />
                <path d="m2 2 20 20" />
            </svg>
        </x-ui.input-group.button>

        <x-ui.input-group.button
            x-on:click="copy()"
            x-bind:aria-label="copied ? '{{ __('Copied') }}' : '{{ __('Copy value') }}'"
        >
            {{-- Clipboard (idle). --}}
            <svg x-show="!copied" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                <rect x="9" y="9" width="11" height="11" rx="2" />
                <path d="M5 15V5a2 2 0 0 1 2-2h10" />
            </svg>
            {{-- Check (copied). --}}
            <svg x-show="copied" x-cloak aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-success">
                <path d="M20 6 9 17l-5-5" />
            </svg>
            <span class="sr-only" x-text="copied ? '{{ __('Copied') }}' : '{{ __('Copy value') }}'"></span>
        </x-ui.input-group.button>
    </x-ui.input-group>
</div>
resources/js/ui/masked-input.js JS
/**
 * Masked input behavior.
 *
 * Holds the credential `value`, the `revealed` masking flag, and a transient
 * `copied` flag. `toggle()` flips masking; `copy()` writes the value to the
 * clipboard via navigator.clipboard and resets `copied` after a short delay so
 * the check icon and "Copied" label are momentary. Guards the clipboard call so
 * insecure/unsupported contexts fail quietly. Self-registers on `alpine:init`.
 */
document.addEventListener('alpine:init', () => {
    window.Alpine.data('uiMaskedInput', (config = {}) => ({
        value: config.value ?? '',
        revealed: false,
        copied: false,
        _timer: null,

        toggle() {
            this.revealed = !this.revealed;
        },

        async copy() {
            try {
                if (navigator.clipboard?.writeText) {
                    await navigator.clipboard.writeText(this.value);
                }
                this.copied = true;
                clearTimeout(this._timer);
                this._timer = setTimeout(() => {
                    this.copied = false;
                }, 1500);
            } catch (error) {
                // Clipboard unavailable (insecure context / denied permission).
                console.warn('masked-input copy failed:', error);
            }
        },

        destroy() {
            clearTimeout(this._timer);
        },
    }));
});

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
2.0.5
Status
Stable
License
open
Accessibility reviewed
No review date recorded
Last breaking change
No date recorded
Deprecation
Not deprecated
Contract
v6
Foundation
≥ 1.0.0