Under the hook, Plume implements component behaviors using the great React-Aria and React-Stately projects. They are exciting new frameworks from Adobe that define accessible state and behavior to your components, without dictating how they should look. You bring the designs, React-Aria brings the accessibility and interactivity, and Plume brings the two together!
While these libraries are not opinionated about how your components should look, they are opinionated about how your components should work, mainly in pursuit of accessibility and consistency across different platforms (mobile, desktop, etc) and different interaction modes (keyboard, pointer, touch, mouse, etc).
To that end, there are a few things to note when using Plume hooks to create Plume components. They are in service of the greater good (consistent accessibility), but may be unconventional:
Many of the event handlers abstract over similar events across keyboards, touches, pointers, mouse, etc. into a unified event. The unified / abstract event is lossy; you don’t have access to the raw underlying event object via their APIs. Specifically, you may still have access to the target DOM element and the held-down modifier keys, but you may not be able to preventDefault() or to stopPropagation().
In general, too, React-Aria’s philosophy is that an event’s propagation should be stopped when it is handled, and since React-Aria’s hooks are handling the event, it has usually already called stopPropagation() on it.
You can take a look at what the different event objects look like.
onClick, clickable / pressable components expose
onPress, which combines all sorts of click-like events from keyboards, touches, pointers, mouse, etc. into a unified pressing event. The PressEvent is also a pretty stripped down object, with no way to preventDefault() or stopPropagation(). You can read more about it here.
Here again, React-Aria tries to abstract over all the different ways in which you can hover over something in different interaction modes. A big part of the work here is actually fighting against the browser’s emulated mouse enter events on touch devices. The philosophy here is that your hover handlers should not be firing on touch devices, as there’s no real way to “hover”, and you shouldn’t have relied on using onMouseEnter/Leave on mobile for useful work. You can read more about it here.
All form controls must be “labeled”. When instantiating one of these components, you should specify one of these three props:
- label ー a React.ReactNode that labels the component. React Aria supplies the right props to the label container ー basically, generating an id that the form element will reference via aria-labelledby.
- aria-label ー if you don’t have a visual label as part of your component, then you can just write out the textual label here.
- aria-labelledby ー else if your label is outside of the component, you need to give that label an id, and pass in an id here. If you do not pass in one of these, then react-aria will print a warning to the console.
See useLabel for more details.