diff --git a/.changeset/true-guests-drive.md b/.changeset/true-guests-drive.md
new file mode 100644
index 0000000000..1452649f83
--- /dev/null
+++ b/.changeset/true-guests-drive.md
@@ -0,0 +1,5 @@
+---
+"@stackoverflow/stacks-svelte": minor
+---
+
+Added aria-label and aria-labelledby props to PopoverContent
diff --git a/packages/stacks-svelte/src/components/Popover/Popover.test.ts b/packages/stacks-svelte/src/components/Popover/Popover.test.ts
index b9fa312335..9f54642965 100644
--- a/packages/stacks-svelte/src/components/Popover/Popover.test.ts
+++ b/packages/stacks-svelte/src/components/Popover/Popover.test.ts
@@ -193,6 +193,75 @@ describe("Popover", () => {
expect(innerContentElement).to.have.class("custom-class");
});
+ it("should add aria-label to the popover when the ariaLabel prop is provided", async () => {
+ render(Popover, {
+ props: {
+ ...defaultProps,
+ autoshow: true,
+ children: createSvelteComponentsSnippet([
+ defaultChildren.reference,
+ {
+ component: PopoverContent,
+ props: {
+ ariaLabel: "Popover with content",
+ children: createRawSnippet(() => ({
+ render: () => "Popover Content",
+ })),
+ },
+ },
+ ]),
+ },
+ });
+
+ expect(screen.getByRole("dialog")).to.have.attribute(
+ "aria-label",
+ "Popover with content"
+ );
+ });
+
+ it("should add aria-labelledby to the popover when the ariaLabelledby prop is provided", async () => {
+ render(Popover, {
+ props: {
+ ...defaultProps,
+ autoshow: true,
+ children: createSvelteComponentsSnippet([
+ defaultChildren.reference,
+ {
+ component: PopoverContent,
+ props: {
+ ariaLabelledby: "my-label-id",
+ children: createRawSnippet(() => ({
+ render: () => "Popover Content",
+ })),
+ },
+ },
+ ]),
+ },
+ });
+
+ expect(screen.getByRole("dialog")).to.have.attribute(
+ "aria-labelledby",
+ "my-label-id"
+ );
+ });
+
+ it("should not add aria-label or aria-labelledby when not provided", async () => {
+ render(Popover, {
+ props: {
+ ...defaultProps,
+ autoshow: true,
+ children: createSvelteComponentsSnippet([
+ defaultChildren.reference,
+ defaultChildren.content,
+ ]),
+ },
+ });
+
+ const dialog = screen.getByRole("dialog");
+ expect(dialog).not.to.have.attribute("aria-label");
+ expect(dialog).not.to.have.attribute("aria-labelledby");
+ });
+
it("add classes to the popover close button component when the class prop is provided", async () => {
render(Popover, {
props: {
diff --git a/packages/stacks-svelte/src/components/Popover/PopoverContent.svelte b/packages/stacks-svelte/src/components/Popover/PopoverContent.svelte
index e50ece48ee..2d1d6979f2 100644
--- a/packages/stacks-svelte/src/components/Popover/PopoverContent.svelte
+++ b/packages/stacks-svelte/src/components/Popover/PopoverContent.svelte
@@ -9,6 +9,14 @@
* (if not specified, it will default to 'dialog' for popovers or 'tooltip' when in tooltip mode)
*/
role?: string | null;
+ /**
+ * Accessible label for the popover
+ */
+ ariaLabel?: string;
+ /**
+ * ID of an element that labels the popover
+ */
+ ariaLabelledby?: string;
/**
* Additional CSS classes added to the s-popover element
*/
@@ -25,6 +33,8 @@
let {
role = null,
+ ariaLabel,
+ ariaLabelledby,
class: className = "",
contentClass = "",
children,
@@ -65,6 +75,8 @@
id={`${pstate.id}-popover`}
class={computedClass}
role={computedRole}
+ aria-label={ariaLabel}
+ aria-labelledby={ariaLabelledby}
use:pstate.floatingContent
use:focusTrap={{ active: pstate.trapFocus && !!pstate.visible }}
use:clickOutside