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

@atlure/ui-web

v0.12.5

Published

Atlure web components: the DOM render layer for the marketing surface, sharing cva variant recipes with @atlure/ui

Readme

@atlure/ui-web

DOM components for atlure-web, the Atlure marketing and SEO surface.

This package is deliberately small. Atlure's product is the Expo app, so product UI belongs in @atlure/ui — not here. What lives here is the handful of components a marketing site actually needs: Button, Card, Badge, Input, Accordion, and the Container / Stack layout primitives. If you are reaching for a booking flow, a message thread or a sitter card, you are in the wrong package.

There is no react-native dependency, direct or transitive.

Install

pnpm add @atlure/ui-web @atlure/tailwind-preset

Wire the preset and the token CSS variables into the consuming app:

const preset = require('@atlure/tailwind-preset');

module.exports = {
  presets: [preset],
  content: ['./src/**/*.{ts,tsx}', './node_modules/@atlure/ui-web/dist/**/*.js'],
};
@import '@atlure/tokens/theme.css';
@tailwind base;
@tailwind components;
@tailwind utilities;

Dark mode is class-based: put dark on <html>.

CSS custom properties

Import the generated token CSS once, at the top of the consuming app's root stylesheet. Both light and dark palettes live in the same file — the dark class on <html> swaps them.

@import '@atlure/tokens/theme.css';

Preventing the light-flash on first paint

The Tailwind preset uses darkMode: "class", so a server-rendered marketing page needs the dark class set before hydration — otherwise it paints light and switches. @atlure/ui-web exports a blocking script snippet that does this synchronously. Inject it in <head> before any stylesheet:

import { themeScript } from '@atlure/ui-web';

<head>
  <script dangerouslySetInnerHTML={{ __html: themeScript }} />
</head>

Then wrap the tree in <ThemeProvider> and expose a <ThemeToggle> (or roll your own via useTheme()).

import { ThemeProvider, ThemeToggle } from '@atlure/ui-web';

<ThemeProvider>
  <ThemeToggle />
</ThemeProvider>

Usage

import { Button, Card, CardContent, CardHeader, CardTitle } from '@atlure/ui-web';

export function SitterTeaser({ name, onBook }: { name: string; onBook: () => void }) {
  return (
    <Card variant="elevated">
      <CardHeader>
        <CardTitle>{name}</CardTitle>
      </CardHeader>
      <CardContent>
        <Button onClick={onBook}>Book</Button>
      </CardContent>
    </Card>
  );
}

Every user-facing string is a prop. This package hardcodes no display text, so the consuming app owns i18n.

Variant recipes are shared, not duplicated

The class-variance-authority recipes are the shared artifact between web and native — NativeWind accepts the same Tailwind class strings on React Native components, so only the render layer (~15 lines per component) differs per platform.

src/variants/
  shared.ts      the cross-platform recipes: button, badge, card, input, accordion
  layout.ts      web-only recipes: container, stack
  index.ts       public barrel, re-exported at @atlure/ui-web/variants

shared.ts is the swap point. @atlure/ui is not published yet; the moment it exposes its recipe subpath, shared.ts becomes a single line:

export * from '@atlure/ui/variants';

and the five recipe files here are deleted. Nothing else in the package changes. Until then, keep the recipes inside src/variants/ restricted to the React Native-safe Tailwind subset — no space-x-*, no divide-*, no grid, no descendant selectors, and flex-row stated explicitly. Web-only escapes (mx-auto, max-w-screen-*, arbitrary variants) belong in layout.ts or in the render layer.

Never redefine a token here. Colors, spacing, radii, type scale and control heights all come from @atlure/tokens by way of @atlure/tailwind-preset.

Components are reviewed in Storybook

Stories live in apps/storybook-web, never in this package — a workbench that cannot be reached from a published package cannot leak into one.

pnpm --filter storybook-web storybook

Scripts

| Script | What it does | | --- | --- | | build | tsup → ESM, CJS and .d.ts in dist/ | | typecheck | tsc --noEmit | | test | Vitest + React Testing Library |

License

MIT © David Moreira

Cross-platform API parity

@atlure/ui-web and @atlure/ui deliberately do not share cva recipes. An earlier plan assumed they could, and that was wrong on the technical merits. Three platform differences make shared class strings impossible:

  • React Native has no CSS pseudo-classes, so hover:, focus-visible: and disabled: have no native equivalent and must be modelled as explicit variant props.
  • React Native has no text style inheritance, so native needs a separate buttonLabelVariants where web simply puts text-primary-foreground on the button and lets children inherit.
  • inline-flex and transition-colors have no meaning in React Native's layout and animation model.

What is shared and enforced is the API surface. src/variants/parity.test.ts parses both platforms' recipe sources and fails if the variant option names drift on any axis both platforms expose. That keeps variant="secondary" and size="md" meaning the same thing everywhere, which is what consumers actually depend on.

Known divergences, deliberately allow-listed

These are recorded in the test so they cannot grow silently. Each is either platform-inherent or a tracked gap:

| Component | Divergence | Why | |---|---|---| | button | native-only isDisabled | RN has no :disabled selector; web uses the pseudo-class | | button | web-only size: icon | Gap. Native should gain an icon size; mobile has icon buttons too | | badge | native-only size | Gap. Web should gain the size axis | | card | web-only padding | Gap. Native should gain the padding axis | | input | native-only isDisabled, isMultiline | RN has no :disabled; multiline is TextInput vs <textarea> |

Two naming inconsistencies were fixed rather than allow-listed, because a single concept under two names is exactly the drift that makes a two-platform system painful: web's controlSize became size to match native, and web's isInteractive became isPressable. The Input component already omits the native HTML size attribute, so the rename is safe.

The rows marked Gap are real work, not accepted design. Closing one means adding the axis to the other platform and removing its entry from the allow-list.