A design system built as a vestibule between the street and the courtyard.
Hashti is named for the octagonal room where a visitor leaves the noise behind. The components carry that pause: clear labels, quiet surfaces, and state you can trust.
Change the room, not the code
44pxminimum control height
4.5:1contrast, both themes
1stylesheet, no runtime
326stories swept every release
Four rooms, one crossing
Persian houses never moved a guest in a straight line. The same sequence organises this library: an announcement, a settling room, a corridor, and the courtyard beyond.
Darvazeh
دروازهA pointed arch marks the boundary. A command states what it will do before the system does it.
Hashti
هشتیThe octagonal room where light settles and attention re-forms. Overlays, focus containment, and quiet state live here.
Dalan
دالانA bent corridor that refuses a straight line of sight. Depth in the system is a passage, never a drop shadow.
Hayati
حیاطیThe courtyard, where the work actually happens. Calm surfaces, honest contrast, and room to stay.
What the system promises
Every promise here is measured in the rendered browser, not asserted in a README. The same checks gate each release.
- Behaviour
- React Aria Components: roving tab index, typeahead, screen-reader semantics.
- Composition
- Trigger, value, and popover stay separate parts instead of one prop matrix.
- Contrast
- Essential controls hold 4.5:1, body text 7:1, in every shipped theme.
- Tokens
- Layered CSS custom properties. No style engine, no hashing, no hydration cost.
- Focus
- A two-pixel ring outside the control, keyboard only, never animated.
- Longevity
- Apache 2.0, one stylesheet, no runtime dependency on the theme package.
Start at the threshold
One stylesheet import, one theme boundary, then compose. Nothing is fetched at runtime and no provider configures a stylesheet.
bun add @hashti/react @hashti/tokens react-aria-components
import "@hashti/tokens/tokens.css";
import "@hashti/react/styles.css";
import { Theme, Button } from "@hashti/react";
export function Threshold() {
return (
<Theme theme="frame-light">
<Button variant="primary" onPress={openPortal}>
Enter portal
</Button>
</Theme>
);
}