diff --git a/docs/pages/components/Combobox.mdx b/docs/pages/components/Combobox.mdx
new file mode 100644
index 000000000..f21caa44d
--- /dev/null
+++ b/docs/pages/components/Combobox.mdx
@@ -0,0 +1,75 @@
+---
+title: Combobox
+description: An input component to display a list of suggested options matching the current text input.
+source: https://github.com/dequelabs/cauldron/tree/develop/packages/react/src/components/Combobox/Combobox.tsx
+---
+
+import { Combobox, ComboboxItem, ComboboxGroup } from '@deque/cauldron-react'
+
+```js
+import {
+ Combobox,
+ ComboboxItem,
+ ComboboxGroup
+} from '@deque/cauldron-react'
+```
+
+## Examples
+
+## With Items
+
+```jsx example
+function ComboboxWithItemPropExample() {
+ const items = [
+ { label: 'Red', value: 'Red' },
+ { label: 'Orange', value: 'Orange' },
+ { label: 'Yellow', value: 'Yellow' },
+ { label: 'Green', value: 'Green' },
+ { label: 'Blue', value: 'Blue' }
+ ]
+ return (
+
+ )
+}
+```
+
+## With Children
+
+```jsx example
+
+ 🍎 Apple
+ 🍌 Banana
+ 🥒 Cucumber
+ 🍊 Orange
+ 🍑 Peach
+ 🍐 Pear
+
+```
+
+## Grouping
+
+```jsx example
+
+
+ Canada
+ Mexico
+ United States
+
+
+ Argentina
+ Bolivia
+ Brazil
+ Chile
+ Columbia
+ Ecuador
+ Falkland Islands
+ French Guiana
+ Guyana
+ Paraguay
+ Peru
+ Suriname
+ Uruguay
+ Venezuela
+
+
+```
\ No newline at end of file
diff --git a/docs/pages/components/Listbox.mdx b/docs/pages/components/Listbox.mdx
new file mode 100644
index 000000000..d99083033
--- /dev/null
+++ b/docs/pages/components/Listbox.mdx
@@ -0,0 +1,170 @@
+---
+title: Listbox
+description: An unstyled component to provide a keyboard navigable list of options.
+source: https://github.com/dequelabs/cauldron/tree/develop/packages/react/src/components/Listbox/Listbox.tsx
+---
+
+import { Listbox, ListboxOption, ListboxGroup } from '@deque/cauldron-react';
+
+```js
+import {
+ Listbox,
+ ListboxOption,
+ ListboxGroup
+} from '@deque/cauldron-react';
+```
+
+## Examples
+
+
+ Listbox follows [aria practices](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/) for _Listbox_ but is not currently intended to be used by itself. This component's intended usage is to be composed with components that have keyboard navigable items like [Combobox](./Combobox) and [OptionsMenu](./OptionsMenu).
+
+
+### Listbox Options
+
+A listbox can contain a list of options with optional values.
+
+```jsx example
+<>
+
Numbers
+
+ One
+ Two
+ Three
+
+>
+```
+
+### Disabled Options
+
+A listbox option can be optionally disabled.
+
+```jsx example
+<>
+ Numbers, but two is disabled
+
+ One
+ Two
+ Three
+
+>
+```
+
+### Grouped Options
+
+Listbox options can also be grouped into categories.
+
+```jsx example
+<>
+ Colors and Numbers
+
+
+ Red
+ Green
+ Blue
+
+
+ One
+ Two
+ Three
+
+
+>
+```
+
+### Keyboard Navigation
+
+By default, keyboard navigation will stop at the first or last option of the list. To wrap around focus to the beginning or the end of the list, `navigation="cycle"` can be set to enable this behavior.
+
+```jsx example
+<>
+ Numbers that Cycle
+
+ One
+ Two
+ Three
+
+>
+```
+
+### Controlled
+
+```jsx example
+<>
+ Controlled Listbox
+
+ One
+ Two
+ Three
+
+>
+```
+
+### Uncontrolled
+
+```jsx example
+<>
+ Uncontrolled Listbox
+
+ One
+ Two
+ Three
+
+>
+```
+
+## Props
+
+### Listbox
+
+
+
+### ListboxOption
+
+
\ No newline at end of file
diff --git a/packages/react/src/components/Combobox/Combobox.tsx b/packages/react/src/components/Combobox/Combobox.tsx
new file mode 100644
index 000000000..65f809ee0
--- /dev/null
+++ b/packages/react/src/components/Combobox/Combobox.tsx
@@ -0,0 +1,132 @@
+import React, {
+ forwardRef,
+ useState,
+ useEffect,
+ useRef,
+ useCallback
+} from 'react';
+import classnames from 'classnames';
+import { useId } from 'react-id-generator';
+import { ContentNode } from '../../types';
+import Listbox from '../Listbox';
+import ComboboxItem from './ComboboxItem';
+
+interface ComboboxItem {
+ key?: string;
+ label: Label;
+ value?: Value;
+}
+
+type Props = {
+ label: ContentNode;
+ items?: ComboboxItem[];
+ value?: ComboboxItem | string | number;
+ defaultValue?: ComboboxItem | string | number;
+} & React.HTMLAttributes>;
+
+function isComboboxItem(value: unknown): value is ComboboxItem {
+ return value !== null && typeof value === 'object' && 'label' in value;
+}
+
+const Combobox = forwardRef(
+ (
+ {
+ id: propId,
+ className,
+ label,
+ children,
+ items = [],
+ value: propValue,
+ defaultValue,
+ ...props
+ },
+ ref
+ ): JSX.Element => {
+ const [value, setValue] = useState(
+ defaultValue || propValue
+ );
+ const [open, setOpen] = useState(false);
+ const [id] = propId ? [propId] : useId(1, 'combobox');
+ const inputRef = useRef(null);
+ const isControlled = typeof value !== 'undefined';
+
+ useEffect(() => {
+ setValue(propValue);
+ }, [propValue]);
+
+ useEffect(() => {
+ const focusHandler = () => {
+ setOpen(true);
+ };
+ const blurHandler = () => setOpen(false);
+
+ inputRef.current?.addEventListener('focus', focusHandler);
+ inputRef.current?.addEventListener('blur', blurHandler);
+
+ return () => {
+ inputRef.current?.removeEventListener('focus', focusHandler);
+ inputRef.current?.removeEventListener('blur', blurHandler);
+ };
+ }, []);
+
+ const comboboxItems =
+ children ||
+ items.map((item, index) => (
+
+ {item.label}
+
+ ));
+
+ const inputValue = isComboboxItem(value) ? value.label : value;
+
+ const handleSelect = useCallback(
+ ({ value: listboxValue }: { value: string | number }) => {
+ if (!isControlled) {
+ setValue(listboxValue);
+ }
+ },
+ [isControlled]
+ );
+
+ return (
+
+
+ {label}
+
+
+
+
+
+
+ {comboboxItems}
+
+
+ );
+ }
+);
+
+Combobox.displayName = 'Combobox';
+
+export default Combobox;
diff --git a/packages/react/src/components/Combobox/ComboboxGroup.tsx b/packages/react/src/components/Combobox/ComboboxGroup.tsx
new file mode 100644
index 000000000..4662364bb
--- /dev/null
+++ b/packages/react/src/components/Combobox/ComboboxGroup.tsx
@@ -0,0 +1,29 @@
+import React, { forwardRef } from 'react';
+import classnames from 'classnames';
+import { ListboxGroup } from '../Listbox';
+import { ContentNode } from '../../types';
+
+interface Props extends React.HTMLAttributes {
+ label: ContentNode;
+}
+
+const ComboboxGroup = forwardRef(
+ ({ className, children, label, ...props }, ref): JSX.Element => {
+ return (
+
+ {children}
+
+ );
+ }
+);
+
+ComboboxGroup.displayName = 'ComboboxGroup';
+
+export default ComboboxGroup;
diff --git a/packages/react/src/components/Combobox/ComboboxItem.tsx b/packages/react/src/components/Combobox/ComboboxItem.tsx
new file mode 100644
index 000000000..7c766b510
--- /dev/null
+++ b/packages/react/src/components/Combobox/ComboboxItem.tsx
@@ -0,0 +1,44 @@
+import React, { forwardRef } from 'react';
+import classnames from 'classnames';
+import { useId } from 'react-id-generator';
+import { ListboxOption, useListboxContext } from '../Listbox';
+import Icon from '../Icon';
+import useSharedRef from '../../utils/useSharedRef';
+
+interface Props extends React.HTMLAttributes {
+ disabled?: boolean;
+}
+
+const ComboboxItem = forwardRef(
+ (
+ { className, children, disabled, id: propId, ...props },
+ ref
+ ): JSX.Element => {
+ const [id] = propId ? [propId] : useId(1, 'combobox-item');
+ const { selected } = useListboxContext();
+ const comboboxItemRef = useSharedRef(ref);
+ const isSelected =
+ selected?.element && selected.element === comboboxItemRef.current;
+
+ return (
+
+ {children}
+ {isSelected ? : null}
+
+ );
+ }
+);
+
+ComboboxItem.displayName = 'ComboboxItem';
+
+export default ComboboxItem;
diff --git a/packages/react/src/components/Combobox/index.ts b/packages/react/src/components/Combobox/index.ts
new file mode 100644
index 000000000..ffd63c6a3
--- /dev/null
+++ b/packages/react/src/components/Combobox/index.ts
@@ -0,0 +1,3 @@
+export { default } from './Combobox';
+export { default as ComboboxItem } from './ComboboxItem';
+export { default as ComboboxGroup } from './ComboboxGroup';
diff --git a/packages/react/src/components/Listbox/Listbox.tsx b/packages/react/src/components/Listbox/Listbox.tsx
new file mode 100644
index 000000000..b12119e6e
--- /dev/null
+++ b/packages/react/src/components/Listbox/Listbox.tsx
@@ -0,0 +1,180 @@
+import React, {
+ forwardRef,
+ useCallback,
+ useState,
+ useLayoutEffect
+} from 'react';
+import { ListboxProvider, ListboxOption } from './ListboxContext';
+import useSharedRef from '../../utils/useSharedRef';
+
+const keys = ['ArrowUp', 'ArrowDown', 'Home', 'End', 'Enter', ' '];
+
+interface ListboxProps
+ extends Omit, 'onSelect'> {
+ as?: React.ElementType | string;
+ value?: unknown;
+ navigation?: 'cycle' | 'bound';
+ onSelect?: ({
+ target,
+ value
+ }: {
+ target: T;
+ value: string | number;
+ }) => void;
+}
+
+const getOptionId = (option: ListboxOption): string | null =>
+ option.element.getAttribute('id');
+
+const isDisabledOption = (option: ListboxOption): boolean =>
+ option.element.getAttribute('aria-disabled') === 'true';
+
+const optionMatchesValue = (option: ListboxOption, value: unknown): boolean =>
+ typeof option.value !== null &&
+ typeof option.value !== 'undefined' &&
+ option.value === value;
+
+const Listbox = forwardRef(
+ (
+ {
+ as: Component = 'ul',
+ children,
+ defaultValue,
+ value,
+ navigation = 'bound',
+ onKeyDown,
+ onFocus,
+ onSelect,
+ ...props
+ },
+ ref
+ ): JSX.Element => {
+ const [options, setOptions] = useState([]);
+ const [activeOption, setActiveOption] = useState(
+ null
+ );
+ const [selectedOption, setSelectedOption] = useState(
+ null
+ );
+ const listboxRef = useSharedRef(ref);
+ const isControlled = typeof value !== 'undefined';
+
+ useLayoutEffect(() => {
+ const listboxValue = isControlled ? value : defaultValue;
+ const selectedOption = options.find(option =>
+ optionMatchesValue(option, listboxValue)
+ );
+ if (selectedOption) {
+ setSelectedOption(selectedOption);
+ }
+ }, [options]);
+
+ const handleSelect = useCallback(
+ (option: ListboxOption) => {
+ setActiveOption(option);
+ if (!isControlled) {
+ setSelectedOption(option);
+ }
+ onSelect?.({ target: option.element, value: option.value as string });
+ },
+ [isControlled]
+ );
+
+ const handleKeyDown = useCallback(
+ (event: React.KeyboardEvent) => {
+ onKeyDown?.(event);
+
+ if (!keys.includes(event.key)) return;
+ event.preventDefault();
+ const enabledOptions = options.filter(
+ option => !isDisabledOption(option)
+ );
+
+ if (!enabledOptions.length) return;
+
+ const [up, down, home, end, enter, space] = keys;
+ const firstOption = enabledOptions[0];
+ const lastOption = enabledOptions[options.length - 1];
+ const currentOption = activeOption || firstOption;
+ const currentIndex = enabledOptions.indexOf(currentOption);
+ const allowCyclicalNavigation = navigation === 'cycle';
+
+ switch (event.key) {
+ case up:
+ const previousOption =
+ currentIndex === 0 && allowCyclicalNavigation
+ ? lastOption
+ : enabledOptions[Math.max(currentIndex - 1, 0)];
+ setActiveOption(previousOption);
+ break;
+ case down:
+ const nextOption =
+ currentIndex === enabledOptions.length - 1 &&
+ allowCyclicalNavigation
+ ? firstOption
+ : enabledOptions[
+ Math.min(currentIndex + 1, enabledOptions.length - 1)
+ ];
+ setActiveOption(nextOption);
+ break;
+ case home:
+ setActiveOption(firstOption);
+ break;
+ case end:
+ setActiveOption(lastOption);
+ break;
+ case enter:
+ case space:
+ setSelectedOption(activeOption);
+ break;
+ }
+ },
+ [options, activeOption, navigation]
+ );
+
+ const handleFocus = useCallback(
+ (event: React.FocusEvent) => {
+ if (!activeOption) {
+ const firstOption = options.find(option => !isDisabledOption(option));
+ if (firstOption) {
+ setActiveOption(firstOption);
+ firstOption.element.focus();
+ }
+ } else if (event.target === listboxRef.current) {
+ activeOption.element.focus();
+ }
+
+ onFocus?.(event);
+ },
+ [options, activeOption]
+ );
+
+ return (
+
+
+ {children}
+
+
+ );
+ }
+);
+
+Listbox.displayName = 'Listbox';
+
+export default Listbox;
diff --git a/packages/react/src/components/Listbox/ListboxContext.tsx b/packages/react/src/components/Listbox/ListboxContext.tsx
new file mode 100644
index 000000000..54678104f
--- /dev/null
+++ b/packages/react/src/components/Listbox/ListboxContext.tsx
@@ -0,0 +1,59 @@
+import React, { createContext, useContext, useMemo } from 'react';
+
+type UnknownElement = T extends Element ? T : HTMLElement;
+type UnknownValue = T extends string ? T : number;
+type ListboxOption = {
+ element: UnknownElement;
+ value?: UnknownValue;
+};
+
+type ListboxContext = {
+ options: T[];
+ active: T | null;
+ selected: T | null;
+ setOptions: React.Dispatch>;
+ onSelect: (option: T) => void;
+};
+
+type ListboxProvider = {
+ children: React.ReactNode;
+} & ListboxContext;
+
+const ListboxContext = createContext({
+ options: [],
+ active: null,
+ selected: null,
+ setOptions: () => null,
+ onSelect: () => {}
+});
+
+function ListboxProvider({
+ options,
+ active,
+ selected,
+ setOptions,
+ onSelect,
+ children
+}: ListboxProvider): JSX.Element {
+ const { Provider } = (ListboxContext as unknown) as React.Context<
+ ListboxContext
+ >;
+ const value: ListboxContext = useMemo(
+ () => ({
+ options,
+ active,
+ selected,
+ setOptions,
+ onSelect
+ }),
+ [options, active, selected, setOptions]
+ );
+
+ return {children} ;
+}
+
+function useListboxContext(): ListboxContext {
+ return useContext(ListboxContext);
+}
+
+export { ListboxProvider, useListboxContext, ListboxOption };
diff --git a/packages/react/src/components/Listbox/ListboxGroup.tsx b/packages/react/src/components/Listbox/ListboxGroup.tsx
new file mode 100644
index 000000000..7cb78c8de
--- /dev/null
+++ b/packages/react/src/components/Listbox/ListboxGroup.tsx
@@ -0,0 +1,37 @@
+import { ContentNode } from '../../types';
+import React, { forwardRef } from 'react';
+import { useId } from 'react-id-generator';
+
+interface ListboxGroupProps extends React.HTMLAttributes {
+ as?: React.ElementType | string;
+ groupLabelProps?: React.HTMLAttributes;
+ label: ContentNode;
+}
+
+const ListboxGroup = forwardRef(
+ (
+ {
+ as: Component = 'ul',
+ children,
+ id: propId,
+ label,
+ groupLabelProps,
+ ...props
+ },
+ ref
+ ): JSX.Element => {
+ const [id] = propId ? [propId] : useId(1, 'listbox-group-label');
+ return (
+
+
+ {label}
+
+ {children}
+
+ );
+ }
+);
+
+ListboxGroup.displayName = 'ListboxGroup';
+
+export default ListboxGroup;
diff --git a/packages/react/src/components/Listbox/ListboxOption.tsx b/packages/react/src/components/Listbox/ListboxOption.tsx
new file mode 100644
index 000000000..9d103955b
--- /dev/null
+++ b/packages/react/src/components/Listbox/ListboxOption.tsx
@@ -0,0 +1,111 @@
+import React, { forwardRef, useEffect, useCallback } from 'react';
+import classnames from 'classnames';
+import { useId } from 'react-id-generator';
+import { useListboxContext } from './';
+import useSharedRef from '../../utils/useSharedRef';
+
+interface ListboxOptionsProps extends React.HTMLAttributes {
+ as?: React.ElementType | string;
+ value?: string | number;
+ disabled?: boolean;
+ activeClass?: string;
+}
+
+function isElementPreceding(a: Element, b: Element) {
+ return !!(b.compareDocumentPosition(a) & Node.DOCUMENT_POSITION_PRECEDING);
+}
+
+const ListboxOption = forwardRef(
+ (
+ {
+ id: propId,
+ className,
+ as: Component = 'li',
+ children,
+ value,
+ disabled,
+ activeClass = 'ListboxOption--active',
+ onClick,
+ onKeyDown,
+ ...props
+ },
+ ref
+ ): JSX.Element => {
+ const { active, selected, setOptions, onSelect } = useListboxContext();
+ const listboxOptionRef = useSharedRef(ref);
+ const [id] = propId ? [propId] : useId(1, 'listbox-option');
+ const isActive = active?.element === listboxOptionRef.current;
+ const isSelected = selected?.element === listboxOptionRef.current;
+ const optionValue =
+ typeof value !== 'undefined'
+ ? value
+ : listboxOptionRef.current?.innerText;
+
+ useEffect(() => {
+ const element = listboxOptionRef.current;
+
+ setOptions(options => {
+ const option = { element, value: optionValue };
+ if (!element) return options;
+
+ // Elements are frequently appended, so check to see if the newly rendered
+ // element follows the last element element first before any other checks
+ if (
+ !options.length ||
+ isElementPreceding(
+ options[options.length - 1].element,
+ option.element
+ )
+ ) {
+ return [...options, option];
+ }
+
+ for (const opt of options) {
+ if (isElementPreceding(element, opt.element)) {
+ const index = options.indexOf(opt);
+ return [
+ ...options.slice(0, index),
+ option,
+ ...options.slice(index)
+ ];
+ }
+ }
+
+ return options;
+ });
+
+ return () => {
+ setOptions(opts => opts.filter(opt => opt.element !== element));
+ };
+ }, [optionValue]);
+
+ const handleClick = useCallback(
+ (event: React.MouseEvent) => {
+ onSelect({ element: listboxOptionRef.current, value: optionValue });
+ onClick?.(event);
+ },
+ [optionValue]
+ );
+
+ return (
+
+ {children}
+
+ );
+ }
+);
+
+ListboxOption.displayName = 'ListboxOption';
+
+export default ListboxOption;
diff --git a/packages/react/src/components/Listbox/index.ts b/packages/react/src/components/Listbox/index.ts
new file mode 100644
index 000000000..7f29d37cc
--- /dev/null
+++ b/packages/react/src/components/Listbox/index.ts
@@ -0,0 +1,4 @@
+export { default } from './Listbox';
+export { default as ListboxOption } from './ListboxOption';
+export { default as ListboxGroup } from './ListboxGroup';
+export { ListboxProvider, useListboxContext } from './ListboxContext';
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index 6c87ba59a..e470b61f7 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -120,6 +120,16 @@ export {
ColumnList
} from './components/TwoColumnPanel';
export { default as Notice } from './components/Notice';
+export {
+ default as Listbox,
+ ListboxOption,
+ ListboxGroup
+} from './components/Listbox';
+export {
+ default as Combobox,
+ ComboboxItem,
+ ComboboxGroup
+} from './components/Combobox';
/**
* Helpers / Utils
diff --git a/packages/styles/combobox.css b/packages/styles/combobox.css
new file mode 100644
index 000000000..755caee23
--- /dev/null
+++ b/packages/styles/combobox.css
@@ -0,0 +1,107 @@
+:root {
+ --combobox-input-border-color: var(--field-border-color);
+ --combobox-input-background-color: var(--field-background-color);
+ --combobox-input-focus-color: var(--field-border-color-focus);
+ --combobox-input-focus-glow-color: var(--field-border-color-focus-glow);
+ --combobox-listbox-border-color: var(--gray-40);
+ --combobox-listbox-background-color: #fff;
+ --combobox-item-background-color-hover: var(--gray-20);
+ --combobox-group-label-background-color: var(--gray-20);
+ --combobox-group-label-font-size: var(--text-size-smaller);
+}
+
+.cauldron--theme-dark :root {
+}
+
+.Combobox {
+ position: relative;
+}
+
+.Combobox input:is(*, :focus, :hover) {
+ border: none;
+ box-shadow: none;
+ margin: initial;
+ padding: var(--space-smallest);
+}
+
+.Combobox__input {
+ position: relative;
+ display: flex;
+ align-items: center;
+ border: 1px solid var(--combobox-input-border-color);
+ border-bottom: 1px solid var(--field-border-color-underline);
+ margin-bottom: var(--space-half);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
+ background-color: var(--combobox-input-background-color);
+}
+
+.Combobox__input:focus-within {
+ border: 1px solid var(--combobox-input-focus-color);
+ box-shadow: 0 0 0 2px var(--combobox-input-focus-color),
+ 0 0 5px var(--combobox-input-focus-glow-color),
+ inset 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.Combobox__arrow {
+ width: 2rem;
+ display: flex;
+ justify-content: center;
+ position: absolute;
+ right: 0;
+}
+
+.Combobox__arrow:before {
+ content: '';
+ display: inline-block;
+ border-top: 7px solid currentColor;
+ border-left: 6px solid transparent;
+ border-right: 6px solid transparent;
+ border-radius: 2px;
+}
+
+.Combobox__listbox {
+ display: none;
+ position: absolute;
+ min-width: 100%;
+ border: 1px solid var(--combobox-listbox-border-color);
+ box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.15);
+ background-color: var(--combobox-listbox-background-color);
+ z-index: var(--z-index-listbox);
+}
+
+.Combobox__listbox:hover {
+ border-color: var(--combobox-listbox-border-color);
+}
+
+.Combobox:focus-within .Combobox__listbox {
+ display: block;
+}
+
+.Combobox__listbox li {
+ list-style-type: none;
+}
+
+.Combobox__listbox li:not(:last-child) {
+ border-bottom: 1px solid var(--combobox-listbox-border-color);
+}
+
+.Combobox__listbox > ul[role='group']:not(:first-child) {
+ border-top: 1px solid var(--combobox-listbox-border-color);
+}
+
+.ComboboxItem {
+ padding: var(--space-smallest);
+ cursor: pointer;
+}
+
+.ComboboxItem--active,
+.ComboboxItem:is(:hover) {
+ background-color: var(--combobox-item-background-color-hover);
+}
+
+.ComboboxGroup__label {
+ background-color: var(--combobox-group-label-background-color);
+ padding: var(--space-smallest);
+ font-size: var(--combobox-group-label-font-size);
+ font-weight: var(--font-weight-bold);
+}
diff --git a/packages/styles/forms.css b/packages/styles/forms.css
index a3e3a1cc2..a8e496b7a 100644
--- a/packages/styles/forms.css
+++ b/packages/styles/forms.css
@@ -58,7 +58,6 @@ textarea,
[role='menuitemcheckbox'],
[role='menuitemradio'],
[role='textbox'],
-[aria-haspopup='listbox'],
[role='listbox'],
[role='spinbutton'] {
width: 100%;
@@ -75,7 +74,6 @@ textarea,
[role='menuitemcheckbox'],
[role='menuitemradio'],
[role='textbox'],
-[aria-haspopup='listbox'],
[role='listbox'],
[role='spinbutton'] {
border: 1px solid var(--field-border-color);
@@ -87,7 +85,6 @@ textarea:focus,
[role='menuitemcheckbox']:focus,
[role='menuitemradio']:focus,
[role='textbox']:focus,
-[aria-haspopup='listbox']:focus,
[role='listbox']:focus,
[role='spinbutton']:focus {
outline: 0;
@@ -103,7 +100,6 @@ textarea:hover,
[role='menuitemcheckbox']:hover,
[role='menuitemradio']:hover,
[role='textbox']:hover,
-[aria-haspopup='listbox']:hover,
[role='listbox']:hover,
[role='spinbutton']:hover {
border: 1px solid var(--field-border-color-hover);
@@ -115,7 +111,6 @@ textarea:focus:hover,
[role='menuitemcheckbox']:focus:hover,
[role='menuitemradio']:focus:hover,
[role='textbox']:focus:hover,
-[aria-haspopup='listbox']:focus:hover,
[role='listbox']:focus:hover,
[role='spinbutton']:focus:hover {
border: 1px solid var(--field-border-color-focus-hover);
@@ -127,7 +122,6 @@ textarea.Field--has-error,
[role='menuitemcheckbox'].Field--has-error,
[role='menuitemradio'].Field--has-error,
[role='textbox'].Field--has-error,
-[aria-haspopup='listbox'].Field--has-error,
[role='listbox'].Field--has-error,
[role='spinbutton'].Field--has-error {
border: 1px solid var(--field-border-color-error);
@@ -147,7 +141,6 @@ textarea.Field--has-error:hover,
[role='menuitemcheckbox'].Field--has-error:hover,
[role='menuitemradio'].Field--has-error:hover,
[role='textbox'].Field--has-error:hover,
-[aria-haspopup='listbox'].Field--has-error:hover,
[role='listbox'].Field--has-error:hover,
[role='spinbutton'].Field--has-error:hover {
border-color: var(--field-border-color-error-hover);
@@ -159,7 +152,6 @@ textarea.Field--has-error:focus,
[role='menuitemcheckbox'].Field--has-error:focus,
[role='menuitemradio'].Field--has-error:focus,
[role='textbox'].Field--has-error:focus,
-[aria-haspopup='listbox'].Field--has-error:focus,
[role='listbox'].Field--has-error:focus,
[role='spinbutton'].Field--has-error:focus {
border: 1px solid var(--field-border-color-error);
@@ -174,12 +166,21 @@ textarea.Field--has-error:focus:hover,
[role='menuitemcheckbox'].Field--has-error:focus:hover,
[role='menuitemradio'].Field--has-error:focus:hover,
[role='textbox'].Field--has-error:focus:hover,
-[aria-haspopup='listbox'].Field--has-error:focus:hover,
[role='listbox'].Field--has-error:focus:hover,
[role='spinbutton'].Field--has-error:focus:hover {
border-color: var(--field-border-color-error-hover);
}
+[role='listbox'] > li,
+[role='listbox'] > [role='group'] > li {
+ list-style-type: none;
+}
+
+[role='listbox']:focus-within > li.ListboxOption--active,
+[role='listbox']:focus-within > [role='group'] > li.ListboxOption--active {
+ box-shadow: 0 0 0 2px var(--field-icon-focus-color);
+}
+
.Error {
color: var(--field-error-text-color);
text-align: left;
diff --git a/packages/styles/index.css b/packages/styles/index.css
index 4bf7cf0fc..77c5ec1db 100644
--- a/packages/styles/index.css
+++ b/packages/styles/index.css
@@ -41,3 +41,4 @@
@import './two-column-panel.css';
@import './accordion.css';
@import './notice.css';
+@import './combobox.css';