Skip to content
Brok UI

Loading…

No results

Editable Card

Open source

An inline-editable dashed text card with a label, an editable textarea, and edit/save affordances.

Version
v2.1.2
Stability
stable
License
MIT
Related
Expandable Card
Card
Collapsible

Preview

Notes

previews.components.editable-card.default.blade.php Blade
<x-ui.editable-card
    label="Notes"
    value="Add any context you want the team to remember. Click the pencil to edit."
    placeholder="Type your notes…"
    class="mx-auto w-full max-w-xl"
/>

Installation

terminal
php artisan ui:add editable-card

Note

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

resources/js/ui/index.js JS
import './editable-card.js';

Registry contract

php artisan ui:add editable-card 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/editable-card.blade.php
  • js resources/js/ui/editable-card.js
Registry dependencies
textarea
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.

editable-card.md
# Brok UI: Editable Card (`editable-card`)

An inline-editable dashed text card with a label, an editable textarea, and edit/save affordances.

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

## Install

```bash
php artisan ui:add editable-card
```

## Usage

```blade
<x-ui.editable-card
    label="Notes"
    value="Add any context you want the team to remember. Click the pencil to edit."
    placeholder="Type your notes…"
    class="mx-auto w-full max-w-xl"
/>
```

## Props

- `label` (string, default `Notes`) — Heading shown above the text and used to build the edit button's accessible name.
- `value` (string, default ``) — Initial text content.
- `placeholder` (string, default `Type here…`) — Text shown when the value is empty, both in read mode and as the textarea placeholder.
- `editable` (bool, default `true`) — When false, hides the edit button and renders the card as read-only text.

## Use when

- Use to group related content into scannable units for browsing, prioritization, or rearrangement.
- Showing a labelled block of free-text notes that a user can edit inline without navigating to a separate form.
- Needing an explicit edit/save/cancel flow so a draft is discarded unless the user confirms it.

## Avoid when

- Do not use card-like structures when dense row-by-row comparison would be clearer in a list or table.
- The field belongs to a larger form that submits as a whole; use textarea directly inside the form.
- The content is read-only; a plain card or surface is lighter weight.

## Anti-patterns

- Turning every object into a card

## Rules

- Use the `<brok:editable-card>` tag (or `<x-ui.editable-card>`) 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/editable-card
- Registry JSON (files, props, contract): https://brokui.dev/r/open/editable-card.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
label string Notes Heading shown above the text and used to build the edit button's accessible name.
value string Initial text content.
placeholder string Type here… Text shown when the value is empty, both in read mode and as the textarea placeholder.
editable bool true When false, hides the edit button and renders the card as read-only text.

Slots

  • default —

Data slots

Stable hooks for CSS overrides and browser tests.

editable-card

Behavior

  • The pencil button swaps the read-only text for a textarea with Save and Cancel actions.
  • Save commits the draft and dispatches an editable-card-change event; Cancel discards the draft and restores the previous text.
  • While editing, the textarea receives focus automatically so typing can start immediately.
  • 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

Card-based organization

Organize browsable objects into scannable units.

Use when

  • Use to group related content into scannable units for browsing, prioritization, or rearrangement.
  • Showing a labelled block of free-text notes that a user can edit inline without navigating to a separate form.
  • Needing an explicit edit/save/cancel flow so a draft is discarded unless the user confirms it.

Avoid when

  • Do not use card-like structures when dense row-by-row comparison would be clearer in a list or table.
  • The field belongs to a larger form that submits as a whole; use textarea directly inside the form.
  • The content is read-only; a plain card or surface is lighter weight.

Use instead

  • Table for exact comparison
  • List for compact scanning

Anti-patterns

  • Turning every object into a card
Anatomy
root
Theming hooks
editable-card

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
Keyboard
native
Focus
managed
  • The card carries an aria-label built from the label prop, and the textarea in edit mode is labelled the same way, so its purpose is announced consistently in both states.
  • The edit button has an explicit accessible name that includes the card's label rather than a bare icon-only name.
  • 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="editable-card-{{ $record->id }}">
    <x-ui.editable-card
        label="Notes"
        value="Add any context you want the team to remember. Click the pencil to edit."
        placeholder="Type your notes…"
        class="mx-auto w-full max-w-xl"
    />
</div>

Validation

Validation support: native. Keep the error message connected with aria-describedby.

livewire-form.blade.php Blade
<form wire:submit="save" class="space-y-2">
    <brok:editable-card
        wire:model="value"
        :aria-invalid="$errors->has('value') ? 'true' : 'false'"
        aria-describedby="value-error"
    />

    @error('value')
        <p id="value-error" role="alert">{{ $message }}</p>
    @enderror

    <brok:button type="submit" wire:loading.attr="disabled">
        <span wire:loading.remove>Save</span>
        <span wire:loading>Saving…</span>
    </brok:button>
</form>

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/editable-card.blade.php Blade
@props([
    'label' => 'Notes',
    'value' => '',
    'placeholder' => 'Type here…',
    'editable' => true,
])

{{--
    Editable card — a dashed card showing a block of text. When editable, a pencil
    button swaps the read-only text for a <textarea> with Save/Cancel. Save commits
    the draft and emits `editable-card-change`; Cancel restores the previous text.
--}}
<div
    x-data="uiEditableCard({ text: @js($value), editable: {{ $editable ? 'true' : 'false' }} })"
    data-slot="editable-card"
    aria-label="{{ __($label) }}"
    {{ $attributes->merge(['class' => 'rounded-lg border border-dashed border-border bg-card/50 p-6 text-foreground']) }}
>
    <div class="mb-2 flex items-center justify-between gap-2">
        <span class="text-xs font-medium uppercase tracking-wide text-muted-foreground">{{ __($label) }}</span>
        @if ($editable)
            <button
                type="button"
                x-show="!editing"
                @click="edit()"
                aria-label="{{ __('Edit :label', ['label' => __($label)]) }}"
                class="inline-flex size-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none"
            >
                <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/></svg>
            </button>
        @endif
    </div>

    <p x-show="!editing" class="whitespace-pre-wrap text-sm text-muted-foreground" x-text="text || @js(__($placeholder))"></p>

    <div x-show="editing" x-cloak class="space-y-2">
        <x-ui.textarea
            x-ref="field"
            x-model="draft"
            :rows="4"
            placeholder="{{ __($placeholder) }}"
            aria-label="{{ __($label) }}"
            class="resize-y"
        />
        <div class="flex justify-end gap-2">
            <button
                type="button"
                @click="cancel()"
                class="rounded-md px-3 py-2 text-sm text-muted-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none"
            >{{ __('Cancel') }}</button>
            <button
                type="button"
                @click="save()"
                class="rounded-md bg-primary px-3 py-2 text-sm text-primary-foreground transition-colors hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none"
            >{{ __('Save') }}</button>
        </div>
    </div>
</div>
resources/js/ui/editable-card.js JS
/**
 * Editable card behavior.
 *
 * Toggles between a read-only display of `text` and an edit mode backed by
 * `draft`. edit() copies text into draft and focuses the textarea; save()
 * commits the draft (dispatching `editable-card-change`); cancel() discards.
 * Self-registers on `alpine:init` so import order does not matter.
 */
document.addEventListener('alpine:init', () => {
    window.Alpine.data('uiEditableCard', (cfg = {}) => ({
        text: cfg.text ?? '',
        editable: cfg.editable ?? true,
        editing: false,
        draft: '',

        edit() {
            if (!this.editable) {
                return;
            }
            this.draft = this.text;
            this.editing = true;
            this.$nextTick(() => this.$refs.field?.focus());
        },

        save() {
            this.text = this.draft;
            this.editing = false;
            this.$el.dispatchEvent(
                new CustomEvent('editable-card-change', {
                    detail: { value: this.text },
                    bubbles: true,
                })
            );
        },

        cancel() {
            this.draft = this.text;
            this.editing = false;
        },
    }));
});

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
2.1.2
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