npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@barqjs/aria

v0.2.0-next.0

Published

Accessible interactions, state and headless components for barq: WAI-ARIA behaviour on fine-grained signals

Readme

@barqjs/aria

Accessible interactions, state and headless components for barq. It is studied from React Aria (adobe/react-spectrum) and rewritten for a framework whose components run once and whose props are Cells. Nothing is styled.

Install

bun add @barqjs/aria @barqjs/core @barqjs/web @barqjs/primitives @barqjs/server
bun add -d @barqjs/vite

The package ships its TypeScript source under the barq export condition, and @barqjs/vite compiles it in your build, for the DOM or the server.

Example

import { ListBox, Option } from "@barqjs/aria";
import { createSignal } from "@barqjs/core";

const [picked, setPicked] = createSignal<Set<string>>(new Set());
const fruits = [
  { id: "apple", name: "Apple" },
  { id: "banana", name: "Banana" },
];

<ListBox
  label="Fruit"
  items={fruits}
  selectionMode="multiple"
  onSelectionChange={(keys) => setPicked(keys as Set<string>)}
>
  {(fruit) => <Option>{fruit.name}</Option>}
</ListBox>;

Two layers ship, and either can be used on its own. listBox() and option() are hooks that return props for elements you write yourself; <ListBox> and <Option> are components that call them. Every component renders the minimum markup its role needs, and everything visual is yours. @barqjs/ui is the styled layer built on top.

Each module is an entry point as well as being re-exported from the root, so @barqjs/aria/listbox and a tree-shaken import from @barqjs/aria cost the same.

Names carry no use prefix

press(), focusRing(), button(), listBox(). A component here runs once and there is no rules-of-hooks to signal, so the prefix would be a lie about when the function may be called.

Collections are data

React Aria builds a collection by rendering the children and collecting what they declare. This package takes items plus a render function:

<Menu items={actions()} onAction={(key) => run(key)}>
  {(action) => <MenuItem>{action.name}</MenuItem>}
</Menu>

A framework whose components run once and whose children are lazy Blocks does not need the two-pass dance. <Menu> builds the collection from items, renders <For> over the nodes, and installs the node on each row's context so <MenuItem> reads its key, value and disabled state from there. Static items are an array literal.

An item with children becomes a section. That is the one thing to know before nesting anything: a submenu's items go under a different key, because children already means "the items of a section".

Props are Cells

Inside a component a prop is an accessor. A JSX call site passes a value and the compiler wraps it:

<Checkbox isDisabled={disabled()} />   // right
<Checkbox isDisabled={() => disabled()} />  // a Cell of a Cell, and wrong

A hook takes MaybeAccessor<T> for every value option, so listBox({ isDisabled: () => x() }, state) and listBox({ isDisabled: true }, state) both work. Hooks return plain objects whose dynamic values are accessors, which is why <button {...buttonProps} /> re-renders nothing.

Writing a component of your own on top of a hook, fromProps(props) turns Incoming<P> into hook options, and callback(props.validate) unwraps a non-on* function prop. Both live in utils.ts. Both exist because a handler and a Cell are the same thing at runtime (a function), so the distinction has to be made at one boundary.

Styling

There is no CSS. State is exposed as data attributes on the elements the components render, so a stylesheet selects on presence:

[data-selected] {
  background: canvas;
}
[data-focus-visible] {
  outline: 2px solid;
}
[data-disabled] {
  opacity: 0.5;
}

The common ones are data-selected, data-focused, data-focus-visible, data-hovered, data-pressed, data-disabled, data-readonly, data-invalid, data-required, data-open, data-placeholder, data-dragging, data-orientation, data-placement, data-empty and data-virtualized. A boolean one is its presence: data-pressed="" when true and absent when false, so the selector is [data-pressed] and never [data-pressed="true"]. Some components also write data-state (open or closed, checked or unchecked), and a placed overlay writes data-side and data-align.

A component forwards what it has no opinion about: the global attributes (dir, lang, hidden, inert, translate), the global events, id, and anything data-*. That is what lets a design system put its own marker on the element it styles without a wrapper element in the way: <Checkbox data-slot="checkbox"> reaches the <label>. A prop the component owns never becomes an attribute, so isDisabled writes data-disabled and not isdisabled="true".

Overlays are portalled

<Popover> and <Modal> render into document.body through barq's <Portal>, keeping their lexical scope so a portalled overlay still reads the providers it was written inside. A popover under an ancestor with overflow: hidden or a transform would otherwise be clipped or anchored against the wrong box.

A root popover renders a display: contents container and offers it to popovers opened from inside it, so a submenu lands in its parent's group: the two count as one overlay for outside-press dismissal and for aria-hidden.

To send overlays somewhere else (an element inside a shadow root, a fullscreen element, a container you style), wrap the tree. getContainer returns an Element; returning null means the body.

<PortalProvider getContainer={() => overlays}>{children}</PortalProvider>

Validation

Every field takes validate and validationBehavior:

<TextField label="Email" validate={(value) => (value.includes("@") ? null : "Not an email")} />
<NumberField label="Quantity" validate={(n) => (n > 0 ? null : "At least one")} />
<ColorField label="Brand" validate={(colour) => (colour.getChannelValue("alpha") < 1 ? "No transparency" : null)} />

validate receives the field's own value: a number for <NumberField>, a Color for <ColorField>, a DateValue for <DateField>, the selected key for <Select>. It is not called for an empty value. null and undefined mean the field is blank, and blank is isRequired's business. Under validationBehavior="native" the page's errors are written onto the hidden control with setCustomValidity, so the browser refuses the submit and focuses the first invalid field.

<Form> provides validationBehavior and server errors to every field inside it.

Virtualisation

A list of ten thousand rows renders the window that is on screen:

<Virtualizer layout={listLayout({ rowHeight: 32 })}>
  <ListBox label="Cities" items={cities()} style={{ height: "300px", overflow: "auto" }}>
    {(city) => <Option>{city.name}</Option>}
  </ListBox>
</Virtualizer>

The collection is whole throughout: selection, typeahead and aria-setsize all see every item, and only the DOM is a window. Keyboard navigation moves by the layout rather than by measuring elements, because Page Down has to reach a row that does not exist yet. Row heights may be fixed (rowHeight) or estimated and measured (estimatedRowHeight). <ListBox>, <GridList> and <Table> all accept it.

Unlike React Aria there is no view recycling: <For> keys rows by item, so a row that stays in the window keeps its element and its state across a scroll.

What is here

Hooks and state, by module:

  • platform: isMac, isIOS, isWebKit and the rest.
  • dom: ownership, shadow-safe contains/activeElement, isFocusable/isTabbable, scrollIntoViewport.
  • utils: mergeProps, id/mergeIds, filterDOMProps, controllable, fromProps, callback, styleProps.
  • interactions: press, hover, focused, focusWithin, keyboard, longPress, move, interactOutside, scrollWheel, focusable, modality/focusVisible, openLink.
  • focus: FocusWalker, createFocusManager, focusScope, focusRing.
  • i18n: useLocale, collator, filter, number, date and list formatters, StringFormatter, NumberParser.
  • live: announce, visuallyHidden.
  • collections: Collection, ListCollection, Selection, SelectionManager, listState.
  • selection: ListKeyboardDelegate, typeSelect, selectableItem, selectableList, selectableCollection.
  • overlays: overlayTrigger, overlay, ariaHideOutside, preventScroll, overlayPosition, modalOverlay.
  • presence: presence and exitDuration, which hold closing content until its exit animation ends.
  • virtualizer: Layout, ListLayout, listLayout, virtualizer, <Virtualizer>.
  • date and color: barq's answers to @internationalized/date and @react-stately/color.
  • validation: formValidationState, fieldValidation, formValidation.
  • toggle and togglegroup: toggle, toggleGroup, toggleGroupItem, toggleGroupState.
  • toast: toastQueue and toastSwipe, the engine behind @barqjs/ui's toaster.
  • drawer, otp, splitter, scroller and navigation: the state behind a swipe-to-dismiss drawer, a one-time-code field, resizable panels, a scroll area and a navigation menu.

Components: Button ToggleButton Checkbox CheckboxGroup Switch RadioGroup Radio Link Separator ProgressBar Meter TextField SearchField NumberField ListBox Option Menu MenuTrigger SubmenuTrigger ContextMenu Menubar Select ComboBox Command GridList Table TagGroup Tabs Toolbar Breadcrumbs Disclosure DisclosureGroup Dialog Modal Popover Slider Calendar RangeCalendar DateField TimeField DatePicker DateRangePicker ColorPicker ColorSlider ColorArea ColorWheel ColorField ColorSwatch Tooltip Form Questionnaire Virtualizer.

What is not here

Deliberately absent:

  • Drag and drop. useDrag, useDrop, the droppable collections and the drop indicators. Nothing in this package is a drop target, and the layout seam that a virtualised drop target needs is not built.
  • React plumbing. useEffectEvent, useObjectRef, useLayoutEffect, useValueEffect, useSyncRef, useDeepMemo, useCachedChildren and the rest exist to work around React's model. @barqjs/primitives covers the ones that are real behaviour: resizeObserver, windowSize, clipboard.
  • Colour contrast and reading order in @barqjs/testing's ariaViolations. A headless DOM cannot answer either, and a wrong answer is worse than none.

Not built yet, in rough order of how much is missing:

  • A Toast region component (the queue is toastQueue; @barqjs/ui renders it). Tree and TreeItem. Autocomplete and SearchAutocomplete. StepList. TokenField. ActionGroup. A ToggleButtonGroup component (the hooks are toggleGroup). Landmark navigation.
  • Selection checkboxes for a grid, gridlist and table (useGridSelectionCheckbox and its siblings), and the selection announcement that goes with them.
  • Load more: useLoadMore and the sentinel, for a collection that pages as it scrolls.
  • Table column resizing.
  • Calendar month and year pickers. The grid and the cells are there, the two dropdowns are not.
  • useColorChannelField, and the gradient helper behind a colour area's background.
  • Time zones and non-Gregorian calendars in date.ts; colour spaces beyond RGB, HSL and HSB in color.ts. Both modules state their limits in their own headers.

Things that will bite

  • Keying a <For> by an object built in the each expression rebuilds every row on every read, and the row holding focus is destroyed under the user. datefield.tsx keys its segments by index for exactly this reason.
  • A callback prop must declare its parameter. callback() tells a handler from a Cell by arity, so validate={() => "Bad"} is read as a Cell and called, and the message becomes the callback. Write validate={(value) => "Bad"}.
  • A component's JSX in a const is built eagerly. const body = <div/> constructs it there and then. Put it in a component or inline it where it belongs, or a conditional above it will not gate it.

License

MIT