webadwaita
v0.2.2
Published
Cross-platform Adwaita-styled UI components built on react-strict-dom.
Readme
WebAdwaita
Cross-platform React component library inspired by GNOME's libadwaita, built on react-strict-dom. One TypeScript codebase renders on web, iOS, and Android.
Why react-strict-dom?
react-strict-dom is Meta's strict subset of <html.*> primitives plus a css.create API that compiles to StyleX on web and React Native styles on native. WebAdwaita uses it so every component is platform-neutral by default — no .web.tsx / .native.tsx forks needed.
Components
| Component | Purpose |
| --- | --- |
| Box | Flex layout primitive (direction / gap / padding / align / justify). |
| Button | Variants: default, suggested, destructive, flat, pill. Sizes: sm / md / lg. |
| Entry | Text input with focus underline (Adwaita AdwEntry). |
| Switch | Pill-shaped toggle with sliding thumb. |
| Checkbox | Square checkbox with optional label. |
| Card | Surface with optional title; boxed and flat variants. |
| HeaderBar | Top bar with leading / centered title / trailing slots. |
Install
npm install webadwaita react-strict-domPeer deps: react ^19, plus react-dom (web) or react-native >= 0.79 (native).
Setup
WebAdwaita ships precompiled JS plus per-component CSS — no special bundler config required. Install, import, render:
npm install webadwaita react-strict-domimport { Button, Card } from 'webadwaita';That's it. Each component side-effect-imports its own CSS chunk, so your bundler ships only the styles for the components you actually use (works in Next.js, Vite, CRA, anywhere with a modern bundler honoring package.json#sideEffects).
Native (Expo / Metro): the package's react-native exports condition routes Metro to a precompiled native build that uses RN StyleSheet at runtime. No Babel preset to wire up in your app — just import { Button } from 'webadwaita'.
How the build works
The library has a build step (npm run build) that produces:
dist/
web/<Component>/index.js ← starts with `import './styles.css'`
web/<Component>/styles.css ← only that component's atomic rules
web/_tokens/styles.css ← shared baseline (loaded once)
native/<Component>/index.js ← RN StyleSheet output, no CSS
types/<Component>/index.d.tsTwo design-token notes carry over to consumer code:
- Tokens still live in
src/tokens.css.ts. The.css.tsfilename is required: react-strict-dom's StyleX config treats.cssas the token-file extension, so any file callingcss.defineConsts/css.defineVarsmust follow that convention. src/theme.tsre-exports the same tokens for runtime use.
Use
import { Box, Button, Card, Entry, Switch } from 'webadwaita';
export function Settings() {
const [name, setName] = useState('');
const [enabled, setEnabled] = useState(true);
return (
<Card title="Profile">
<Box gap="md">
<Entry placeholder="Your name" value={name} onChange={setName} />
<Box direction="row" align="center" gap="sm">
<Switch checked={enabled} onChange={setEnabled} />
<span>Enabled</span>
</Box>
<Button variant="suggested">Save</Button>
</Box>
</Card>
);
}Develop
# library
npm install
npm run build # produces dist/{web,native,types}
npm run typecheck
# web Storybook (Vite-powered)
npm --prefix apps/storybook-web install
npm run storybook:web # opens http://localhost:6006
# native Storybook (Expo + @storybook/react-native v8, on-device UI)
npm --prefix apps/storybook-native install
npm run storybook:native # then press i / a / w in the Expo CLIThe native app embeds the Storybook UI directly — open the Expo Go app on a phone (or a simulator) and pick a component from the on-device sidebar. After adding/removing .stories.tsx files, regenerate the registry:
npm --prefix apps/storybook-native run storybook-generateLicense
MIT — see LICENSE. Inspired by react-strict-dom's apps/example-ui/ and the libadwaita design system.
