feat: consistent and accessible modal on delete
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
carla 2026-02-26 11:17:21 +01:00
parent 2922a4d1ee
commit e422e5f4ef
10 changed files with 424 additions and 102 deletions

View file

@ -3011,24 +3011,53 @@ end
- [ ] Skip links are available
- [ ] Tables have proper structure (th, scope, caption)
- [ ] ARIA labels used for icon-only buttons
- [ ] Modals/dialogs: focus moves into modal, aria-labelledby, keyboard dismiss (Escape)
### 8.11 DaisyUI Accessibility
### 8.11 Modals and Dialogs
DaisyUI components are designed with accessibility in mind, but ensure:
Use a consistent, keyboard-accessible pattern for all confirmation and form modals (e.g. delete role, delete group, delete data field, edit cycle). Do not rely on `data-confirm` (browser `confirm()`) for destructive actions; use a LiveView-controlled `<dialog>` so focus and semantics are correct (WCAG 2.4.3, 2.1.2).
**Structure and semantics:**
- Use `<dialog>` with DaisyUI classes `modal modal-open` when the modal is visible.
- Add `role="dialog"` and `aria-labelledby` pointing to the modal titles `id` so screen readers announce the dialog and its purpose.
- Give the title (e.g. `<h3>`) a unique `id` (e.g. `id="delete-role-modal-title"`).
**Focus management (WCAG 2.4.3):**
- When the modal opens, move focus into the dialog. Use `phx-mounted={JS.focus()}` on the first focusable element:
- If the modal has an input (e.g. confirmation text), put `phx-mounted={JS.focus()}` on that input (e.g. delete data field, delete group).
- If the modal has only buttons (e.g. confirm/cancel), put `phx-mounted={JS.focus()}` on the Cancel (or first) button so the user can Tab to the primary action and confirm with the keyboard.
- This ensures that after choosing "Delete role" (or similar) with the keyboard, focus is inside the modal and the user can confirm or cancel without using the mouse.
**Layout and consistency:**
- Use `modal-box` for the content container and `modal-action` for the button row (Cancel + primary action).
- Place Cancel (or neutral) first, primary/danger action second.
- For destructive actions that require typing a confirmation string, use the same pattern as the delete data field modal: label, value to type, single input, then modal-action buttons.
**Closing:**
- Cancel button closes the modal (e.g. `phx-click="cancel_delete_modal"`).
- Optionally support Escape to close via `phx-window-keydown` on the LiveView/LiveComponent.
**Reference implementation:** Delete data field modal in `CustomFieldLive.IndexComponent` (input + `phx-mounted={JS.focus()}` on input; `aria-labelledby` on dialog). Delete role modal in `RoleLive.Show` (no input; `phx-mounted={JS.focus()}` on Cancel button).
### 8.12 DaisyUI Accessibility
DaisyUI components are designed with accessibility in mind. For modals and dialogs, follow §8.11 (Modals and Dialogs). Example structure:
```heex
<!-- Modal accessibility -->
<dialog id="my-modal" class="modal" aria-labelledby="modal-title">
<!-- Modal: use dialog + aria-labelledby + focus on first focusable (see §8.11) -->
<dialog id="my-modal" class="modal modal-open" role="dialog" aria-labelledby="my-modal-title">
<div class="modal-box">
<h2 id="modal-title"><%= gettext("Confirm Deletion") %></h2>
<h3 id="my-modal-title" class="text-lg font-bold"><%= gettext("Confirm Deletion") %></h3>
<p><%= gettext("Are you sure?") %></p>
<div class="modal-action">
<button class="btn" onclick="document.getElementById('my-modal').close()">
<.button variant="neutral" phx-click="cancel" phx-mounted={JS.focus()}>
<%= gettext("Cancel") %>
</button>
<button class="btn btn-error" phx-click="confirm-delete">
<%= gettext("Delete") %>
</button>
</.button>
<.button variant="danger" phx-click="confirm_delete"><%= gettext("Delete") %></.button>
</div>
</div>
</dialog>