Fix duplicate HTML ID warnings in form components

Resolve browser console warnings about non-unique HTML IDs when multiple
Livewire components with similar form fields appear on the same page.

**Problem:**
Multiple forms using generic IDs like `id="description"` or `id="name"`
caused duplicate ID warnings and potential accessibility/JavaScript issues.

**Solution:**
- Separate `wire:model` binding name from HTML `id` attribute
- Auto-prefix HTML IDs with Livewire component ID for uniqueness
- Preserve existing `wire:model` behavior with property names

**Implementation:**
- Added `$modelBinding` property for wire:model (e.g., "description")
- Added `$htmlId` property for unique HTML ID (e.g., "lw-xyz123-description")
- Updated render() method to generate unique IDs automatically
- Updated all blade templates to use new properties

**Components Updated:**
- Input (text, password, etc.)
- Textarea (including Monaco editor)
- Select
- Checkbox
- Datalist (single & multiple selection)

**Result:**
 All HTML IDs now unique across page
 No console warnings
 wire:model bindings work correctly
 Validation error messages display correctly
 Backward compatible - no changes needed in existing components

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2025-10-14 10:27:41 +02:00
parent 56481b31bc
commit a514c837b6
10 changed files with 118 additions and 31 deletions

View File

@@ -25,8 +25,8 @@
</label>
@endif
@if ($useMonacoEditor)
<x-forms.monaco-editor id="{{ $id }}" language="{{ $monacoEditorLanguage }}" name="{{ $name }}"
name="{{ $id }}" model="{{ $value ?? $id }}" wire:model="{{ $value ?? $id }}"
<x-forms.monaco-editor id="{{ $htmlId }}" language="{{ $monacoEditorLanguage }}" name="{{ $name }}"
name="{{ $modelBinding }}" model="{{ $value ?? $modelBinding }}" wire:model="{{ $value ?? $modelBinding }}"
readonly="{{ $readonly }}" label="dockerfile" />
@else
@if ($type === 'password')
@@ -45,34 +45,34 @@
@endif
<input x-cloak x-show="type === 'password'" value="{{ $value }}"
{{ $attributes->merge(['class' => $defaultClassInput]) }} @required($required)
@if ($id !== 'null') wire:model={{ $id }} @endif
@if ($modelBinding !== 'null') wire:model={{ $modelBinding }} @endif
wire:dirty.class="dark:ring-warning ring-warning" wire:loading.attr="disabled"
type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $id }}"
type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $htmlId }}"
name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}"
aria-placeholder="{{ $attributes->get('placeholder') }}">
<textarea minlength="{{ $minlength }}" maxlength="{{ $maxlength }}" x-cloak x-show="type !== 'password'"
placeholder="{{ $placeholder }}" {{ $attributes->merge(['class' => $defaultClass]) }}
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $id }}"
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $modelBinding }}"
@else
wire:model={{ $value ?? $id }}
wire:model={{ $value ?? $modelBinding }}
wire:dirty.class="dark:ring-warning ring-warning" @endif
@disabled($disabled) @readonly($readonly) @required($required) id="{{ $id }}"
name="{{ $name }}" name={{ $id }}></textarea>
@disabled($disabled) @readonly($readonly) @required($required) id="{{ $htmlId }}"
name="{{ $name }}" name={{ $modelBinding }}></textarea>
</div>
@else
<textarea minlength="{{ $minlength }}" maxlength="{{ $maxlength }}"
{{ $allowTab ? '@keydown.tab=handleKeydown' : '' }} placeholder="{{ $placeholder }}"
{{ !$spellcheck ? 'spellcheck=false' : '' }} {{ $attributes->merge(['class' => $defaultClass]) }}
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $id }}"
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $modelBinding }}"
@else
wire:model={{ $value ?? $id }}
wire:model={{ $value ?? $modelBinding }}
wire:dirty.class="dark:ring-warning ring-warning" @endif
@disabled($disabled) @readonly($readonly) @required($required) id="{{ $id }}"
name="{{ $name }}" name={{ $id }}></textarea>
@disabled($disabled) @readonly($readonly) @required($required) id="{{ $htmlId }}"
name="{{ $name }}" name={{ $modelBinding }}></textarea>
@endif
@endif
@error($id)
@error($modelBinding)
<label class="label">
<span class="text-red-500 label-text-alt">{{ $message }}</span>
</label>