Skip to content

fs-dialog: forward host <dialog> attributes (aria-labelledby / aria-describedby) via dialog.open() options #67

@Krypt0nBull3t

Description

@Krypt0nBull3t

Context

fs-dialog (@script-development/fs-dialog) renders the host <dialog> element with no attribute-forwarding hook:

// node_modules/@script-development/fs-dialog/dist/index.mjs:51-64
h("dialog", {style, onClick, onCancel, onVnodeMounted}, h(Suspense, ...))

dialog.open(component, props) forwards props only to the inner component, never to the <dialog> host. As a result, screen readers announce these dialogs as a generic "dialog" with no accessible name or description until the consumer manually walks closest('dialog') from a template ref and calls setAttribute(...) itself.

Repro / why it matters

Surfaced in ublgenie PR #167 (UBL-0027, Dialog a11y): both ConfirmDialog and FormDialog had to wire aria-labelledby (Phase 1) and aria-describedby on ConfirmDialog (Phase 2) consumer-side, like this:

onMounted(() => {
    const hostDialog = root.value?.closest('dialog');
    hostDialog?.setAttribute('aria-labelledby', titleId);
    hostDialog?.setAttribute('aria-describedby', messageId);
});

Every fs-packages-adopting territory will hit the same pattern — at least 6 territories on the war-room map will need the same workaround. Goosterhof flagged this as a non-blocking ask on PR #167 (review observation 3): "the wiring genuinely belongs in fs-dialog as an aria-labelledby option on dialog.open()".

Proposed shape

Extend dialog.open() (and any sibling open-shape APIs) to accept ARIA host options that get applied to the <dialog> element itself, not the inner component. Sketch:

dialog.open(MyDialog, props, {
    ariaLabelledBy: 'my-title-id',
    ariaDescribedBy: 'my-message-id',
});

Inside fs-dialog, set these on the <dialog> vnode in the same h("dialog", {...}) call. Optional shape variant: accept ariaLabel directly for label-only cases that don't need a title element.

Acceptance

  • dialog.open() accepts host-level ARIA attributes alongside component props.
  • Existing call sites that don't pass the new options continue to work unchanged.
  • Once shipped, ublgenie's closest('dialog')?.setAttribute(...) calls in ConfirmDialog.vue / FormDialog.vue can be deleted in favor of passing ids through the open API.

References

  • Back-to-code/ublgenie-app PR #167 (Dialog a11y wiring) — consumer-side workaround that this issue would obsolete.
  • WAI-ARIA Authoring Practices — Dialog (Modal) Pattern: aria-labelledby + aria-describedby are the canonical pairing for native <dialog> elements.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions