Skip to content
Nana UI

A filter component for data tables, and a sharper tab strip

View .md Download

· Release

This release adds the filter toolbar that sits above a data table, sharpens the segmented tab control, and fixes a hover state that had never actually rendered.

Filtering a table needs a search box, a status cut, a date window, and often a column picker — four different controls that all have to end up in one query. The new components collect them without replacing any of them.

Each filter is a nana-filter-item wrapping whichever field suits it. The values are read by duck-typing (value, or checked for switches and checkboxes) rather than by tag name, so a field this library doesn’t ship works too, as long as it exposes a value and fires nana-change or change.

All Open Pending Closed

The aggregated value is a plain object keyed by each item’s name — active filters only:

filter.addEventListener("nana-filter-change", (e) => {
loadOrders(new URLSearchParams(e.detail.value)); // { q: "acme", status: "open" }
});
// Restoring from the URL writes through to every field, without echoing an event back.
filter.value = Object.fromEntries(new URLSearchParams(location.search));

default-value is what stops a chip appearing immediately. A select sitting on All holds a non-empty value, so without it the filter counts as active from first paint and shows a chip forever. Set it to whatever means “no filter” and the item stays inactive until the user moves off it. clear() returns the field there too.

Chips resolve option labels. A chip reads Status is Open, not status: open — the item looks up the chosen nana-option and uses its text. A date range’s start/end is split back into two readable ends, and a multi-select collapses into a single removable token rather than one chip per value.

no-chip covers the things that aren’t really filters. A column picker belongs in the query but doesn’t narrow the result set, so rendering “Columns: Ref, Customer, Total” as a removable chip is misleading. Mark it no-chip and it contributes its value while staying out of the chip row, out of the active count, and out of Clear all — nobody clearing their filters means to reset which columns they can see.

All Open Ref Customer Total

Three layouts — bar for sitting directly above a table, panel for card chrome and a responsive grid, popover for a trigger button carrying the active count. mode="manual" buffers edits behind an Apply / Reset footer when each change costs a round trip.

See Filter and Filter Item.

Segmented tab segments now sit flush. The 0.25rem gap between them is gone. A gap opened grey channels through the track, which read as several separate controls rather than one strip with a moving thumb. The thumb’s corner radius is now derived from the track’s radius minus its padding, so the two curves stay concentric instead of the thumb looking pasted into a rounder hole. Both are themeable via --nana-tab-group-track-radius and --nana-tab-group-track-pad.

Disabled tabs recede by opacity, not by colour. They previously used the subtle text step; they now use the inactive colour at 45% opacity, so they stay distinguishable from a merely unselected tab in both themes.

Tab labels are fractionally tighter. Tab text now carries the tight letter-spacing token. Very wide tab strips may reflow by a pixel or two.

The segmented control got a proper raised thumb: a three-layer shadow — a tight contact shadow, a wider ambient one, and a hairline edge — that tightens as the thumb compresses under a press. The track carries an inset hairline so it reads as recessed rather than as a flat grey rectangle.

One thing we tried and backed out: pushing unselected labels further into the background. Measured on the segmented track, the subtle text step gives 3.98:1 at the 14px label size — under the 4.5:1 AA floor. The track is what breaks it; on a bare page the same colour scrapes 4.74:1. So unselected labels stay on the muted step (6.55:1 on the track), and selection is carried by the gap to the active state instead — full-strength text on an opaque thumb at 17.9:1. --nana-tab-inactive-color is there if you want to recess it against a background you’ve checked yourself.

nana-input’s hover border never rendered. The @media (hover: hover) guard trailed the selector instead of wrapping the rule:

/* A selector followed by @media is not valid nesting — the whole block was dropped. */
:host(:not([disabled])) @media (hover: hover) { .wrapper:hover { … } }

Malformed CSS inside a Lit css “ template doesn’t fail the TypeScript build, so this sat there silently. The field now shows its border affordance on hover before focus.

--nana-transition-easing-emphasizedcubic-bezier(0.32, 0.72, 0, 1), a decelerating curve for movement that should settle rather than stop dead. The tab indicator and the segmented thumb both use it.

0.0.19 and 0.0.20 ship identical code. The per-package release script was named publish, which is also an npm lifecycle hook — so npm publish uploaded the package and then invoked itself against the version it had just created. The resulting 409 Conflict made a successful release look like a failed one. The script is renamed, and 0.0.20 is the version to depend on.