diff --git a/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.md b/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.md
index 362d4694e7b3cd4b63e725d1ce9ac32cf5b4696b..ee92c00cd55cdfb84cda78c6d7279cca6c0a24cf 100644
--- a/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.md
+++ b/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.md
@@ -67,7 +67,7 @@ It's possible to close the disclosure dropdown programmatically by calling the `
`close` methods on the disclosure dropdown via a template ref. For example:
```js
-this.$refs.disclosureDropdown.closeAndFocus()
+this.$refs.disclosureDropdown.closeAndFocus();
```
The `closeAndFocus` method is preferred in most cases, especially when triggering it from some action
@@ -181,3 +181,28 @@ In this case the user is responsible for handling all events and navigation insi
## Split dropdown
See [button group documentation](/docs/base-button-group--docs#split-dropdowns).
+
+## Container element type
+
+The disclosure dropdown automatically determines the appropriate container element type based on its
+content:
+
+- When the dropdown contains only list items (`gl-disclosure-dropdown-item`,
+ `gl-disclosure-dropdown-group`, or direct `li` elements), it renders a semantic `
` container.
+- When the dropdown contains any non-list content, it renders a `` container instead.
+
+This automatic detection ensures proper semantic HTML is used when the dropdown primarily functions
+as a list.
+
+### Forcing a list container
+
+In some cases, you may have custom components that ultimately render list items but aren't detected
+by the automatic logic. For these situations, use the `force-list-container` prop to explicitly
+render a `
` container:
+
+```html
+
+ Item 1
+ Item 2
+
+```
diff --git a/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.spec.js b/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.spec.js
index 6d05cd513c536e6dc3c9fafc898d8cbbcfbdc446..29ae8b6795551eba065cc3b257bb20faf9e27e37 100644
--- a/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.spec.js
+++ b/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.spec.js
@@ -364,6 +364,18 @@ describe('GlDisclosureDropdown', () => {
expect(findDisclosureContent().element.tagName).toBe('DIV');
});
+ it('should render `ul` as content tag when forceListContainer is true, even with non-list content', () => {
+ const slots = {
+ default: `
+ Non-list item content
+ More non-list content
+ `,
+ };
+
+ buildWrapper({ forceUlTag: true }, { slots });
+ expect(findDisclosureContent().element.tagName).toBe('UL');
+ });
+
describe('discouraged usage', () => {
it('should render `ul` as content tag when default slot contains LI tags', () => {
const slots = {
diff --git a/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.vue b/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.vue
index 36481e2ebe3d1cf9e73f70898862e2b0f2417884..ec6f6e916b2d9eec2f00720cf697794ecc76bb3c 100644
--- a/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.vue
+++ b/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.vue
@@ -228,6 +228,14 @@ export default {
required: false,
default: false,
},
+ /**
+ * Force the dropdown to use a UL container regardless of content
+ */
+ forceUlTag: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -238,6 +246,7 @@ export default {
computed: {
disclosureTag() {
if (
+ this.forceUlTag ||
this.items?.length ||
// eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
hasOnlyListItems(this.$scopedSlots.default || this.$slots.default)