Repository: dnbexperience/eufemia
Pull request #5106: fix(Forms): support `required` in Field.Selection with `dropdown` variant
Author: login: langz
Labels: name: released
Created: 2025-05-12T10:52:29Z | Merged: 2025-05-15T08:32:52Z

TODO:
- [x] Extensive testing using VoiceOver and NVDA 


Diff:
diff --git a/packages/dnb-eufemia/src/components/dropdown/Dropdown.js b/packages/dnb-eufemia/src/components/dropdown/Dropdown.js
index 35a6ba1cde1..85cbd4fe677 100644
--- a/packages/dnb-eufemia/src/components/dropdown/Dropdown.js
+++ b/packages/dnb-eufemia/src/components/dropdown/Dropdown.js
@@ -241,7 +241,7 @@ export default class Dropdown extends React.PureComponent {
         {...this.props}
         id={id}
         data={data || children}
-        opened={null}
+        opened={false}
         tagName="dnb-dropdown"
         ignore_events={false}
         prevent_selection={
@@ -641,6 +641,8 @@ class DropdownInstance extends React.PureComponent {
                       </span>
                     </>
                   }
+                  role="combobox"
+                  title={title}
                   {...triggerParams}
                 />
               )}
diff --git a/packages/dnb-eufemia/src/components/dropdown/__tests__/Dropdown.test.tsx b/packages/dnb-eufemia/src/components/dropdown/__tests__/Dropdown.test.tsx
index ca0e2a07127..46efcafb965 100644
--- a/packages/dnb-eufemia/src/components/dropdown/__tests__/Dropdown.test.tsx
+++ b/packages/dnb-eufemia/src/components/dropdown/__tests__/Dropdown.test.tsx
@@ -75,6 +75,12 @@ describe('Dropdown component', () => {
     ).toBe(
       (mockData[props.value] as DrawerListDataArrayObject).selected_value
     )
+    expect(
+      document.querySelector('.dnb-dropdown__trigger')
+    ).toHaveAttribute(
+      'title',
+      (mockData[props.value] as DrawerListDataArrayObject).selected_value
+    )
 
     keydown(32) // space
 
@@ -101,6 +107,13 @@ describe('Dropdown component', () => {
       (mockData[(props.value as number) + 1] as DrawerListDataArrayObject)
         .selected_value
     )
+    expect(
+      document.querySelector('.dnb-dropdown__trigger')
+    ).toHaveAttribute(
+      'title',
+      (mockData[(props.value as number) + 1] as DrawerListDataArrayObject)
+        .selected_value
+    )
   })
 
   it('has correct value on key search', () => {
@@ -768,9 +781,18 @@ describe('Dropdown component', () => {
 
   it('has correct "aria-expanded"', () => {
     render(<Dropdown {...props} data={mockData} />)
+
+    expect(
+      document
+        .querySelector('span.dnb-dropdown')
+        .querySelector('button.dnb-dropdown__trigger')
+        .getAttribute('aria-expanded')
+    ).toBe('false')
+
     open()
 
     const elem = document.querySelector('span.dnb-dropdown')
+
     expect(
       elem
         .querySelector('button.dnb-dropdown__trigger')
@@ -780,6 +802,131 @@ describe('Dropdown component', () => {
     expect(elem.getAttribute('class')).toContain('dnb-dropdown--opened')
   })
 
+  it('has correct "role"', () => {
+    render(<Dropdown {...props} data={mockData} />)
+
+    expect(
+      document
+        .querySelector('span.dnb-dropdown')
+        .querySelector('button.dnb-dropdown__trigger')
+        .getAttribute('role')
+    ).toBe('combobox')
+  })
+
+  it('has correct "aria-haspopup"', () => {
+    render(<Dropdown {...props} data={mockData} />)
+
+    expect(
+      document
+        .querySelector('span.dnb-dropdown')
+        .querySelector('button.dnb-dropdown__trigger')
+        .getAttribute('aria-haspopup')
+    ).toBe('listbox')
+  })
+
+  it('has correct "aria-haspopup" when action_menu', () => {
+    render(<Dropdown {...props} data={mockData} action_menu />)
+
+    expect(
+      document
+        .querySelector('span.dnb-dropdown')
+        .querySelector('button.dnb-dropdown__trigger')
+        .getAttribute('aria-haspopup')
+    ).toBe('true')
+  })
+
+  it('has correct "aria-haspopup" when more_menu', () => {
+    render(<Dropdown {...props} data={mockData} more_menu />)
+
+    expect(
+      document
+        .querySelector('span.dnb-dropdown')
+        .querySelector('button.dnb-dropdown__trigger')
+        .getAttribute('aria-haspopup')
+    ).toBe('true')
+  })
+
+  it('has correct "aria-haspopup" when prevent_selection', () => {
+    render(<Dropdown {...props} data={mockData} prevent_selection />)
+
+    expect(
+      document
+        .querySelector('span.dnb-dropdown')
+        .querySelector('button.dnb-dropdown__trigger')
+        .getAttribute('aria-haspopup')
+    ).toBe('true')
+  })
+
+  it('has correct "role" in options', () => {
+    render(
+      <Dropdown skip_portal no_animation opened={true} data={mockData} />
+    )
+
+    render(
+      <Dropdown skip_portal no_animation opened={true} data={mockData} />
+    )
+
+    expect(
+      document
+        .querySelector('.dnb-drawer-list__options')
+        .getAttribute('role')
+    ).toBe('listbox')
+  })
+
+  it('has correct "role" in options when action_menu', () => {
+    render(
+      <Dropdown
+        skip_portal
+        no_animation
+        opened={true}
+        data={mockData}
+        action_menu
+      />
+    )
+
+    expect(
+      document
+        .querySelector('.dnb-drawer-list__options')
+        .getAttribute('role')
+    ).toBe('menu')
+  })
+
+  it('has correct "role" in options when more_menu', () => {
+    render(
+      <Dropdown
+        skip_portal
+        no_animation
+        opened={true}
+        data={mockData}
+        more_menu
+      />
+    )
+
+    expect(
+      document
+        .querySelector('.dnb-drawer-list__options')
+        .getAttribute('role')
+    ).toBe('menu')
+  })
+
+  it('has correct "role" in options when prevent_selection', () => {
+    render(
+      <Dropdown
+        skip_portal
+        no_animation
+        opened={true}
+        data={mockData}
+        prevent_selection
+      />
+    )
+
+    expect(
+      document
+        .querySelector('.dnb-drawer-list__options')
+        .getAttribute('role')
+    ).toBe('menu')
+  })
+
   it('should set aria-activedescendant to be of first option, when no items are selected', () => {
     render(<Dropdown data={['1', '2']} />)
     open()
@@ -1134,7 +1281,7 @@ describe('Dropdown component', () => {
     )
   })
 
-  it('has a default title if no value is given', () => {
+  it('has a default value if no value is given', () => {
     const title = 'Make a selection'
     render(<Dropdown data={mockData} title={title} {...mockProps} />)
     expect(
@@ -1178,6 +1325,70 @@ describe('Dropdown component', () => {
     ).toHaveTextContent('Title as prop')
   })
 
+  it('has correct title', () => {
+    render(<Dropdown {...props} data={mockData} />)
+    expect(
+      document.querySelector('.dnb-dropdown__trigger')
+    ).toHaveAttribute(
+      'title',
+      (mockData[props.value] as DrawerListDataArrayObject).selected_value
+    )
+  })
+
+  it('has correct title after new selection', () => {
+    render(<Dropdown {...props} data={mockData} />)
+    open()
+
+    // then simulate changes
+    keydown(40) // down
+    expect(
+      document.querySelector('.dnb-dropdown__trigger')
+    ).toHaveAttribute(
+      'title',
+      (mockData[props.value] as DrawerListDataArrayObject).selected_value
+    )
+  })
+
+  it('has correct title after useEffect value state change', () => {
+    const newValue = 3
+    const UpdateValue = () => {
+      const [value, setValue] = React.useState(props.value)
+
+      React.useEffect(() => {
+        setValue(newValue)
+      }, [])
+
+      return <Dropdown {...props} data={mockData} value={value} />
+    }
+
+    render(<UpdateValue />)
+
+    expect(
+      document.querySelector('.dnb-dropdown__trigger')
+    ).toHaveAttribute(
+      'title',
+      (mockData[newValue] as DrawerListDataArrayObject).selected_value
+    )
+
+    open()
+
+    const options = document.querySelectorAll('li.dnb-drawer-list__option')
+    expect(options[newValue].classList).toContain(
+      'dnb-drawer-list__option--selected'
+    )
+    expect(options[newValue].classList).toContain(
+      'dnb-drawer-list__option--focus'
+    )
+  })
+
+  it('has a default title if no value is given', () => {
+    const title = 'Make a selection'
+    render(<Dropdown data={mockData} title={title} {...mockProps} />)
+    expect(
+      document.querySelector('.dnb-dropdown__trigger')
+    ).toHaveAttribute('title', title)
+  })
+
   it('should support inline styling', () => {
     render(<Dropdown data={mockData} style={{ color: 'red' }} />)
 
@@ -1189,6 +1400,10 @@ describe('Dropdown component', () => {
   it('should support empty data entry', () => {
     render(<Dropdown skip_portal no_animation data={['']} />)
 
+    expect(
+      document.querySelector('button').getAttribute('aria-expanded')
+    ).toBe('false')
+
     keydown(32) // space
 
     expect(
@@ -1369,6 +1584,9 @@ describe('Dropdown component', () => {
     const trigger = document.querySelector('.dnb-dropdown__trigger')
     const suffix = document.querySelector('.dnb-dropdown__suffix')
 
+    expect(dropdown).not.toHaveClass('dnb-dropdown--opened')
+    expect(trigger).toHaveAttribute('aria-expanded', 'false')
+
     await userEvent.click(trigger)
 
     expect(dropdown).toHaveClass('dnb-dropdown--opened')
@@ -1400,13 +1618,7 @@ describe('Dropdown markup', () => {
       <Dropdown {...snapshotProps} data={mockData} />
     )
 
-    expect(
-      await axeComponent(CheckComponent, {
-        rules: {
-          'aria-required-children': { enabled: false },
-        },
-      })
-    ).toHaveNoViolations()
+    expect(await axeComponent(CheckComponent)).toHaveNoViolations()
   })
 })
 
diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Selection/__tests__/Selection.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Selection/__tests__/Selection.test.tsx
index c18e46f3533..2e23bf9730c 100644
--- a/packages/dnb-eufemia/src/extensions/forms/Field/Selection/__tests__/Selection.test.tsx
+++ b/packages/dnb-eufemia/src/extensions/forms/Field/Selection/__tests__/Selection.test.tsx
@@ -24,7 +24,7 @@ describe('Selection', () => {
         <Field.Option value="bar">Bar</Field.Option>
       </Field.Selection>
     )
-    expect(screen.queryAllByRole('button').length).toEqual(1)
+    expect(screen.queryAllByRole('combobox').length).toEqual(1)
     expect(screen.getByText('Bar')).toBeInTheDocument()
     expect(screen.queryAllByRole('option').length).toEqual(0)
     expect(screen.queryByText('Foo')).not.toBeInTheDocument()
@@ -133,7 +133,7 @@ describe('Selection', () => {
       </Field.Selection>
     )
 
-    const btn1 = screen.getByRole('button')
+    const btn1 = screen.getByRole('combobox')
     expect(within(btn1).getByText('Bar')).toBeInTheDocument()
     expect(within(btn1).queryByText('Foo')).not.toBeInTheDocument()
 
@@ -146,7 +146,7 @@ describe('Selection', () => {
     )
 
     // The selected button should now show the other option based on the value-prop change
-    const btn2 = screen.getByRole('button')
+    const btn2 = screen.getByRole('combobox')
     expect(within(btn2).getByText('Foo')).toBeInTheDocument()
     expect(within(btn2).queryByText('Bar')).not.toBeInTheDocument()
   })
@@ -161,7 +161,7 @@ describe('Selection', () => {
       </Field.Selection>
     )
 
-    const selectionButton = screen.getByRole('button')
+    const selectionButton = screen.getByRole('combobox')
     await userEvent.click(selectionButton)
 
     expect(screen.getAllByRole('option').length).toEqual(4)
@@ -1583,6 +1583,7 @@ describe('variants', () => {
             variant="dropdown"
             required
             validateInitially
+            dropdownProps={{ skipPortal: true }}
           >
             <Field.Option value="foo">Foo</Field.Option>
             <Field.Option value="bar">Bar</Field.Option>
@@ -1591,15 +1592,7 @@ describe('variants', () => {
 
         openDropdown()
 
-        expect(
-          await axeComponent(result, {
-            rules: {
-              // Because of aria-controls and aria-required is not allowed on buttons – but VO still reads it
-              'aria-allowed-attr': { enabled: false },
-              'aria-valid-attr-value': { enabled: false },
-            },
-          })
-        ).toHaveNoViolations()
+        expect(await axeComponent(result)).toHaveNoViolations()
       })
 
       it('should have aria-required', () => {
@@ -2317,7 +2310,7 @@ describe('event handlers', () => {
       </Field.Selection>
     )
 
-    const selectionButton = screen.getByRole('button')
+    const selectionButton = screen.getByRole('combobox')
     await userEvent.click(selectionButton)
 
     await waitFor(async () => {
@@ -2348,7 +2341,7 @@ describe('event handlers', () => {
       </Field.Selection>
     )
 
-    const selectionButton = screen.getByRole('button')
+    const selectionButton = screen.getByRole('combobox')
     await userEvent.click(selectionButton)
 
     await waitFor(() => {
@@ -2366,7 +2359,7 @@ describe('event handlers', () => {
       </Field.Selection>
     )
 
-    const selectionButton = screen.getByRole('button')
+    const selectionButton = screen.getByRole('combobox')
     await userEvent.click(selectionButton)
 
     await waitFor(async () => {
@@ -2389,7 +2382,7 @@ describe('validation and error handling', () => {
             <Field.Option value="bar">Bar</Field.Option>
           </Field.Selection>
         )
-        const selectionButton = screen.getByRole('button')
+        const selectionButton = screen.getByRole('combobox')
         await userEvent.click(selectionButton)
 
         expect(screen.getByRole('alert')).toBeInTheDocument()
@@ -2402,7 +2395,7 @@ describe('validation and error handling', () => {
             <Field.Option value="bar">Bar</Field.Option>
           </Field.Selection>
         )
-        const selectionButton = screen.getByRole('button')
+        const selectionButton = screen.getByRole('combobox')
         await userEvent.click(selectionButton)
 
         await waitFor(async () => {
diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Selection/stories/Selection.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Selection/stories/Selection.stories.tsx
index 1c95c5c31f8..5cd97085aca 100644
--- a/packages/dnb-eufemia/src/extensions/forms/Field/Selection/stories/Selection.stories.tsx
+++ b/packages/dnb-eufemia/src/extensions/forms/Field/Selection/stories/Selection.stories.tsx
@@ -273,3 +273,17 @@ export function SelectionButtonsAriaRequired() {
     </Field.Selection>
   )
 }
+
+export function SelectionAriaRequired() {
+  return (
+    <Field.Selection
+      label="Label"
+      variant="dropdown"
+      required
+      validateInitially
+    >
+      <Field.Option value="foo">Foo</Field.Option>
+      <Field.Option value="bar">Bar</Field.Option>
+    </Field.Selection>
+  )
+}


Reviews:
user: login: langz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T10:51:22Z
user: login: langz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T10:57:22Z
user: login: langz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T12:04:01Z
user: login: tujoworker
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-14T13:42:43Z

Review comments:
diff_hunk: @@ -241,7 +241,7 @@ export default class Dropdown extends React.PureComponent {
         {...this.props}
         id={id}
         data={data || children}
-        opened={null}
+        opened={false}
path: packages/dnb-eufemia/src/components/dropdown/Dropdown.js
user: login: langz
body: Setting this to false instead of null will lead to `aria-expanded="false"` on initial render, instead of how it is today, where the `aria-expanded` attribute is not rendered.
created_at: 2025-05-13T10:51:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -639,13 +641,14 @@ class DropdownInstance extends React.PureComponent {
                       </span>
                     </>
                   }
+                  role="combobox"
path: packages/dnb-eufemia/src/components/dropdown/Dropdown.js
user: login: langz
body: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/combobox_role
created_at: 2025-05-13T10:57:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -639,6 +639,8 @@ class DropdownInstance extends React.PureComponent {
                       </span>
                     </>
                   }
+                  role="combobox"
+                  title={title}
path: packages/dnb-eufemia/src/components/dropdown/Dropdown.js
user: login: langz
body: After enabling the ARIA tests again, I would get this error, hence adding title to the dropdown trigger button:
```
    "Buttons must have discernible text (button-name)"

    Fix any of the following:
      Element does not have inner text that is visible to screen readers
      aria-label attribute does not exist or is empty
      aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty
      Element has no title attribute
      Element's default semantics were not overridden with role="none" or role="presentation"
```
created_at: 2025-05-13T12:04:01Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-05-12T10:52:33Z
body: [vc]: #vXr5tR9G/+BQVoE624zcqUDy+mBX+thIOR4f1WNcoPA=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJldWZlbWlhIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2V1ZmVtaWEvZXVmZW1pYS81eW05ejllUjc0ZVhNcnQ4WjZ2eWlSb0tQZXNMIiwicHJldmlld1VybCI6ImV1ZmVtaWEtZ2l0LWZpeC1zZWxlY3Rpb24tYTExeS1pc3N1ZXMtdmFyaWFudC1kcm9wZG93bi1ldWZlbWlhLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiZXVmZW1pYS1naXQtZml4LXNlbGVjdGlvbi1hMTF5LWlzc3Vlcy12YXJpYW50LWRyb3Bkb3duLWV1ZmVtaWEudmVyY2VsLmFwcCJ9LCJyb290RGlyZWN0b3J5IjpudWxsfV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **eufemia** | ✅ Ready ([Inspect](https://vercel.com/eufemia/eufemia/5ym9z9eR74eXMrt8Z6vyiRoKPesL)) | [Visit Preview](https://eufemia-git-fix-selection-a11y-issues-variant-dropdown-eufemia.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/eufemia-git-fix-selection-a11y-issues-variant-dropdown-eufemia.vercel.app?via=pr-comment-feedback-link) | May 14, 2025 1:34pm |


author_association: NONE
user: login: codesandbox-ci[bot]
created_at: 2025-05-12T11:00:19Z
body: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/dnbexperience/eufemia/pr/5106/builds/616376) or the icon next to each commit SHA.
author_association: NONE
user: login: tujoworker
created_at: 2025-05-14T08:40:11Z
body: We can't use `role="combobox"` on a Dropdown, because e.g. VoiceOver reads out that the user can "type". So it can be used for an "Autocomplete".

<img width="595" alt="Screenshot 2025-05-14 at 10 38 28" src="https://github.com/user-attachments/assets/9e15e593-50e0-4920-8dc5-e33a11e3e991" />

author_association: MEMBER
user: login: langz
created_at: 2025-05-14T11:03:18Z
body: > We can't use `role="combobox"` on a Dropdown, because e.g. VoiceOver reads out that the user can "type". So it can be used for an "Autocomplete".
> 
> <img alt="Screenshot 2025-05-14 at 10 38 28" width="595" src="https://private-user-images.githubusercontent.com/1501870/443569750-9e15e593-50e0-4920-8dc5-e33a11e3e991.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDcyMjA0MTcsIm5iZiI6MTc0NzIyMDExNywicGF0aCI6Ii8xNTAxODcwLzQ0MzU2OTc1MC05ZTE1ZTU5My01MGUwLTQ5MjAtOGRjNS1lMzNhMTFlM2U5OTEucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MDUxNCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTA1MTRUMTA1NTE3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZDQ0ZTIwYjdmOWFkYTgzYzM4MGQxYTZkY2NmZmI0OWI3ZWVhZTJkZjk5NGZjN2U5ZDExZDM1M2RhN2NiMWYyOSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ._G6f7KlMvSYoSYA2ifUbCEtGyLi9fPrpkuJGtiJJBd4">

Okok, one can sort of type a bit in a Dropdown, when dropdown is active one can type one letter and the hover/selected item will change.

I see that there's different libraries and examples where dropdowns use the combobox role.

https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/ and https://mui.com/material-ui/react-select/

_The combobox pattern supports several optional behaviors. The one that most shapes interaction is text input. Some comboboxes allow users to type and edit text in the combobox and others do not. If a combobox does not support text input, it is referred to as select-only, meaning the only way users can set its value is by selecting a value in the popup._
https://www.w3.org/WAI/ARIA/apg/patterns/combobox/

Or perhaps the `role` should be `listbox`?
https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/listbox_role

author_association: CONTRIBUTOR
user: login: tujoworker
created_at: 2025-05-14T12:45:22Z
body: We do already use `role="listbox"` in the list that opens.

`combobox` describes that it should be combined with an `input` and a list to select something from. We should not use it in the Dropdown.

If aria-required is the problem, we can also think of making an exception, and not set it there. But rather add an `aria-describedby="error-msg"`, where the error-msg describes the "requirement".
author_association: MEMBER
user: login: langz
created_at: 2025-05-14T12:59:55Z
body: > We do already use `role="listbox"` in the list that opens.
> 
> `combobox` describes that it should be combined with an `input` and a list to select something from. We should not use it in the Dropdown.
> 
> If aria-required is the problem, we can also think of making an exception, and not set it there. But rather add an `aria-describedby="error-msg"`, where the error-msg describes the "requirement".

_The combobox input field can either be a single-line text field that supports editing and typing, similar to a HTML `<input>` with a `<datalist>`, or an element that only displays the current value of the combobox._ - https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/combobox_role#description

https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/select:
<img width="798" alt="image" src="https://github.com/user-attachments/assets/3139f817-fe96-4245-8986-6ca3248c4e9f" />


 https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/:
` The following example implementation of the [Combobox Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/) demonstrates a single-select combobox widget that is functionally similar to an HTML select element. Unlike the editable combobox examples, this select-only combobox is not made with an <input> element, and it does not accept freeform user input. However, like an HTML <select>, users can type characters to select matching options.`



But I'm fine with not using role `combobox` as well, not sure what the alternative would be though 🤔 
author_association: CONTRIBUTOR
user: login: tujoworker
created_at: 2025-05-14T13:41:32Z
body: Right. https://www.w3.org/TR/wai-aria-1.2/#combobox also says something similar:

> or an element with role="presentation" that allows the user to select a value from a collection of possible values.

And her we have a coded example: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/

So I guess, even VoiceOver does read out loud that the user can type, we can go for it.
author_association: MEMBER
user: login: langz
created_at: 2025-05-14T17:58:57Z
body: > Right. https://www.w3.org/TR/wai-aria-1.2/#combobox also says something similar:
> 
> > or an element with role="presentation" that allows the user to select a value from a collection of possible values.
> 
> And her we have a coded example: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
> 
> So I guess, even VoiceOver does read out loud that the user can type, we can go for it.

Great, thanks for taking the time to test 🔉 and look into the docs 📖 
author_association: CONTRIBUTOR
user: login: tujoworker
created_at: 2025-05-19T10:13:36Z
body: :tada: This PR is included in version 10.74.0 :tada:

The release is available on:
- [npm package (@latest dist-tag)](https://www.npmjs.com/package/@dnb/eufemia/v/10.74.0)
- [GitHub release](https://github.com/dnbexperience/eufemia/releases/tag/v10.74.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: MEMBER