combobox popover component

I'm playing around with the html popover and css anchor positioning apis to create a dropdown menu. I was just wondering if anyone could have a look at this stripped down code (almost all styling has been removed) and let me know how you could improve it (not looking for adding customization, just semantics, performance, accessibility type stuff). One thing that doesn't quite seem right to be is the use of a button for each option in the drop down, but I don't want to just use a div with click and key-press events on it. I'm pretty sure I don't need to add aria-controls and aria-expanded as the popover api controls that.
import { useId, useRef, useState } from "react";

type Option = { name: string; value: string };

export const Dropdown = ({ name, value, options }: { name: string; value: string; options: Option[] }) => {
const [selectedOption, setSelectedOption] = useState<Option | null>(null);
const popoverId = useId();
const anchorName = `--anchor-${popoverId.replaceAll(":", "-")}`;

const popoverRef = useRef<HTMLDivElement>(null);

return (
<div>
<input type="hidden" name={name} value={value} />

<button
role="combobox"
type="button"
popoverTarget={popoverId}
popoverTargetAction="toggle"
style={{
anchorName,
}}
>
{selectedOption?.name}
</button>

<div
role="group"
popover="auto"
id={popoverId}
ref={popoverRef}
style={{
positionAnchor: anchorName,
}}
>
<div role="listbox">
{options.map((option) => (
<div key={option.value} role="listitem">
<button
role="option"
aria-selected={selectedItem === item}
onClick={() => {
setSelectedOption(option);
popoverRef.current!.hidePopover();
}}
>
<div>{option.name}</div>
</button>
</div>
))}
</div>
</div>
</div>
);
};
import { useId, useRef, useState } from "react";

type Option = { name: string; value: string };

export const Dropdown = ({ name, value, options }: { name: string; value: string; options: Option[] }) => {
const [selectedOption, setSelectedOption] = useState<Option | null>(null);
const popoverId = useId();
const anchorName = `--anchor-${popoverId.replaceAll(":", "-")}`;

const popoverRef = useRef<HTMLDivElement>(null);

return (
<div>
<input type="hidden" name={name} value={value} />

<button
role="combobox"
type="button"
popoverTarget={popoverId}
popoverTargetAction="toggle"
style={{
anchorName,
}}
>
{selectedOption?.name}
</button>

<div
role="group"
popover="auto"
id={popoverId}
ref={popoverRef}
style={{
positionAnchor: anchorName,
}}
>
<div role="listbox">
{options.map((option) => (
<div key={option.value} role="listitem">
<button
role="option"
aria-selected={selectedItem === item}
onClick={() => {
setSelectedOption(option);
popoverRef.current!.hidePopover();
}}
>
<div>{option.name}</div>
</button>
</div>
))}
</div>
</div>
</div>
);
};
1 Reply
EnderTheNetrunner
What's stopping you from making the Options an object rather than an array of objects that are just name/value pairs? Wait nevermind those are your choices not your options.

Did you find this page helpful?