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

@getufy/flint-ui-react

v0.8.8

Published

React wrappers for @getufy/flint-ui web components

Readme

@getufy/flint-ui-react

npm license

React wrappers for @getufy/flint-ui web components, auto-generated with @lit/react.

Installation

npm install @getufy/flint-ui @getufy/flint-ui-react

Peer dependencies: react ^18 || ^19, @getufy/flint-ui ^0.6.0

Setup

Required: Import the theme CSS once in your app entry point. Without this, components render unstyled.

// main.tsx — must be at the top
import '@getufy/flint-ui/theme.css';
import '@getufy/flint-ui/theme-dark.css';  // Required for dark mode support

Missing theme.css = unstyled components. Missing theme-dark.css = no dark mode. In development, a console warning will alert you if theme CSS is missing.

Resources

Subpath imports — use these for tree-shaking

Always import from subpaths, not the barrel. Each component is its own entry point; your bundler only includes the components you actually import.

// Load only what you need
import { FlintButton } from '@getufy/flint-ui-react/button';
import { FlintSelect, FlintOption } from '@getufy/flint-ui-react/select';
import { FlintTabs, FlintTab, FlintTabPanel } from '@getufy/flint-ui-react/tabs';

Subcomponents are co-exported

Each subpath exports all related subcomponents, so you never need to hunt for separate imports:

// All dialog sub-components from one import
import {
  FlintDialog,
  FlintDialogTitle,
  FlintDialogContent,
  FlintDialogActions,
} from '@getufy/flint-ui-react/dialog';

// All tabs sub-components from one import
import { FlintTabs, FlintTab, FlintTabPanel } from '@getufy/flint-ui-react/tabs';

See the Available subpaths table below for every subpath and its exports.

Why subpath imports matter

Lit's @customElement() decorator is a side effect — it registers the custom element globally as soon as the module is evaluated. The barrel import (@getufy/flint-ui-react) causes all 60+ components to register, even ones you never render.

// Registers ALL 60+ components (~897 KB uncompressed)
import { FlintButton } from '@getufy/flint-ui-react';

// Registers only the button component
import { FlintButton } from '@getufy/flint-ui-react/button';

Available subpaths

| Subpath | Exports include | |---|---| | @getufy/flint-ui-react/accordion | FlintAccordion, FlintAccordionItem | | @getufy/flint-ui-react/alert | FlintAlert | | @getufy/flint-ui-react/app-bar | FlintAppBar | | @getufy/flint-ui-react/autocomplete | FlintAutocomplete | | @getufy/flint-ui-react/avatar | FlintAvatar | | @getufy/flint-ui-react/backdrop | FlintBackdrop | | @getufy/flint-ui-react/badge | FlintBadge | | @getufy/flint-ui-react/bottom-navigation | FlintBottomNavigation, FlintBottomNavigationItem | | @getufy/flint-ui-react/box | FlintBox | | @getufy/flint-ui-react/breadcrumbs | FlintBreadcrumbs, FlintBreadcrumbItem | | @getufy/flint-ui-react/button | FlintButton | | @getufy/flint-ui-react/card | FlintCard | | @getufy/flint-ui-react/carousel | FlintCarousel, FlintCarouselItem | | @getufy/flint-ui-react/checkbox | FlintCheckbox | | @getufy/flint-ui-react/chip | FlintChip | | @getufy/flint-ui-react/collapsible | FlintCollapsible, FlintCollapsibleTrigger, FlintCollapsibleContent | | @getufy/flint-ui-react/command | FlintCommand, FlintCommandInput, FlintCommandList, FlintCommandItem | | @getufy/flint-ui-react/container | FlintContainer | | @getufy/flint-ui-react/copy-button | FlintCopyButton | | @getufy/flint-ui-react/date-field | FlintDateField | | @getufy/flint-ui-react/date-picker | FlintDatePicker | | @getufy/flint-ui-react/date-range-picker | FlintDateRangePicker | | @getufy/flint-ui-react/dialog | FlintDialog, FlintDialogTitle, FlintDialogContent, FlintDialogContentText, FlintDialogActions | | @getufy/flint-ui-react/divider | FlintDivider | | @getufy/flint-ui-react/drawer | FlintDrawer | | @getufy/flint-ui-react/empty | FlintEmpty | | @getufy/flint-ui-react/fab | FlintFab | | @getufy/flint-ui-react/format-date | FlintFormatDate | | @getufy/flint-ui-react/format-number | FlintFormatNumber | | @getufy/flint-ui-react/grid | FlintGrid, FlintGridItem | | @getufy/flint-ui-react/hover-card | FlintHoverCard, FlintHoverCardTrigger, FlintHoverCardContent | | @getufy/flint-ui-react/image-comparer | FlintImageComparer | | @getufy/flint-ui-react/image-list | FlintImageList, FlintImageListItem | | @getufy/flint-ui-react/input | FlintInput | | @getufy/flint-ui-react/input-otp | FlintInputOtp, FlintInputOtpGroup, FlintInputOtpSlot | | @getufy/flint-ui-react/item | FlintItem | | @getufy/flint-ui-react/kbd | FlintKbd | | @getufy/flint-ui-react/link | FlintLink | | @getufy/flint-ui-react/list | FlintList, FlintListItem | | @getufy/flint-ui-react/menu | FlintMenu, FlintMenuItem | | @getufy/flint-ui-react/menubar | FlintMenubar, FlintMenubarItem | | @getufy/flint-ui-react/navigation-menu | FlintNavigationMenu, FlintNavigationMenuItem | | @getufy/flint-ui-react/pagination | FlintPagination | | @getufy/flint-ui-react/paper | FlintPaper | | @getufy/flint-ui-react/progress | FlintProgress | | @getufy/flint-ui-react/radio | FlintRadio, FlintRadioGroup | | @getufy/flint-ui-react/rating | FlintRating | | @getufy/flint-ui-react/relative-time | FlintRelativeTime | | @getufy/flint-ui-react/resizable | FlintResizable, FlintResizablePanel, FlintResizableHandle | | @getufy/flint-ui-react/scroll-area | FlintScrollArea | | @getufy/flint-ui-react/select | FlintSelect, FlintOption | | @getufy/flint-ui-react/skeleton | FlintSkeleton | | @getufy/flint-ui-react/slider | FlintSlider | | @getufy/flint-ui-react/snackbar | FlintSnackbar | | @getufy/flint-ui-react/sonner | FlintSonner | | @getufy/flint-ui-react/speed-dial | FlintSpeedDial, FlintSpeedDialAction | | @getufy/flint-ui-react/split-panel | FlintSplitPanel | | @getufy/flint-ui-react/stack | FlintStack | | @getufy/flint-ui-react/stepper | FlintStepper, FlintStepperItem | | @getufy/flint-ui-react/switch | FlintSwitch | | @getufy/flint-ui-react/table | FlintTable, FlintTableRow, FlintTableCell | | @getufy/flint-ui-react/tabs | FlintTabs, FlintTab, FlintTabPanel | | @getufy/flint-ui-react/text-field | FlintTextField | | @getufy/flint-ui-react/textarea | FlintTextarea | | @getufy/flint-ui-react/time-picker | FlintTimePicker | | @getufy/flint-ui-react/toggle | FlintToggle, FlintToggleGroup | | @getufy/flint-ui-react/tooltip | FlintTooltip | | @getufy/flint-ui-react/transfer-list | FlintTransferList | | @getufy/flint-ui-react/tree-view | FlintTreeView, FlintTreeItem | | @getufy/flint-ui-react/typography | FlintTypography | | @getufy/flint-ui-react/visually-hidden | FlintVisuallyHidden |

For exact named exports in any subpath, check the TypeScript declarations in node_modules/@getufy/flint-ui-react/dist/<name>.d.ts.

Basic usage

// main.tsx
import '@getufy/flint-ui/theme.css';
import '@getufy/flint-ui/suppress-warnings'; // optional — see below

// MyComponent.tsx
import { FlintButton } from '@getufy/flint-ui-react/button';

export function MyComponent() {
  return (
    <FlintButton variant="primary" onClick={() => console.log('clicked')}>
      Click me
    </FlintButton>
  );
}

Events

Custom events map to camelCase on* props following the pattern onFlint[ComponentName][EventType]:

| Lit event | React prop | |---|---| | flint-pagination-change | onFlintPaginationChange | | flint-select-change | onFlintSelectChange | | flint-dialog-close | onFlintDialogClose | | flint-menu-close | onFlintMenuClose |

The component name is always included in the event prop. For example, FlintPagination fires onFlintPaginationChange, not onFlintPageChange or onFlintChange. Each wrapper has a strict TypeScript props interface -- using a wrong event name produces a compile-time error:

// TS error: Property 'onFlintPageChange' does not exist on type '...'
<FlintPagination onFlintPageChange={() => {}} />

// Correct:
<FlintPagination onFlintPaginationChange={(e) => console.log(e.detail.page)} />
<FlintSelect
  label="Fruit"
  onFlintSelectChange={(e) => console.log(e.detail.value)}
/>

Event Name Reference

| Component | Lit Event | React Prop | Detail Type | |-----------|-----------|------------|-------------| | Input | flint-input-input | onFlintInputInput | { value: string } | | Input | flint-input-change | onFlintInputChange | { value: string } | | Select | flint-select-change | onFlintSelectChange | { value: string, multiple: false } \| { value: string[], multiple: true } | | Dialog | flint-dialog-open | onFlintDialogOpen | { open: true } | | Dialog | flint-dialog-close | onFlintDialogClose | { open: false } | | Tabs | flint-tabs-change | onFlintTabsChange | { value: string } | | Switch | flint-switch-change | onFlintSwitchChange | { checked: boolean } | | Checkbox | flint-checkbox-change | onFlintCheckboxChange | { checked: boolean } | | Drawer | flint-drawer-open | onFlintDrawerOpen | { open: true } | | Drawer | flint-drawer-close | onFlintDrawerClose | { open: false } |

Timing: onFlintInputInput fires on every keystroke (real-time). onFlintInputChange fires on blur (committed value). Most other events fire after the component's internal state has updated.

Slots

Web components use slots for content projection. In React, pass the slot attribute on a child element to target a named slot. Children without a slot attribute go into the default (unnamed) slot.

// prefix / suffix slots — common pattern for icons in buttons, inputs, etc.
<FlintButton>
  <svg slot="prefix" width="16" height="16"><path d="..." /></svg>
  Click me
  <svg slot="suffix" width="16" height="16"><path d="..." /></svg>
</FlintButton>
// Named layout slots — AppBar example
import { FlintAppBar } from '@getufy/flint-ui-react/app-bar';
import { FlintButton } from '@getufy/flint-ui-react/button';

<FlintAppBar>
  <div slot="navigation">
    <FlintButton variant="icon">☰</FlintButton>
  </div>
  <div slot="title">My App</div>
  <div slot="actions">
    <FlintButton variant="icon">⚙</FlintButton>
  </div>
</FlintAppBar>

Tip: To slot a React component (not a plain HTML element), wrap it in a <span> or <div> with the slot attribute — React does not forward slot through component boundaries.

Each component documents its available slots in the API docs. Common slot names: prefix / suffix (Button, Input); navigation, title, actions (AppBar); trigger / content (Dialog, HoverCard).

Dark mode

Import the dark theme CSS alongside the base theme:

import '@getufy/flint-ui/theme.css';
import '@getufy/flint-ui/theme-dark.css';

Activate dark mode using FlintTheme (recommended) or manual DOM attributes:

import { FlintTheme } from '@getufy/flint-ui-react/theme';

// FlintTheme at the root level auto-sets data-theme on <html>
function App() {
  const [dark, setDark] = useState(false);
  return (
    <FlintTheme mode={dark ? 'dark' : 'light'}>
      {/* your app */}
    </FlintTheme>
  );
}

Or control manually via DOM attributes:

// Toggle dark mode
document.documentElement.setAttribute('data-theme', 'dark');
// Force light mode (overrides OS dark preference)
document.documentElement.setAttribute('data-theme', 'light');

The dark theme CSS also includes a @media (prefers-color-scheme: dark) block, so it automatically applies when the user's OS is set to dark mode. To opt out of automatic detection, set data-theme="light" or use <FlintTheme mode="light">.

Controlled Components

Flint UI components support controlled mode via value + an event handler. The component does not manage its own state when value is set.

Input

onFlintInputInput fires on every keystroke (real-time); onFlintInputChange fires on blur (committed value).

import { useState } from 'react';
import { FlintInput } from '@getufy/flint-ui-react/input';

function ControlledInput() {
  const [value, setValue] = useState('');

  return (
    <FlintInput
      label="Name"
      value={value}
      onFlintInputInput={(e) => setValue(e.detail.value)}   // real-time
      // or: onFlintInputChange={(e) => setValue(e.detail.value)}  // on blur
    />
  );
}

Select

onFlintSelectChange fires when the selection changes. e.detail.value is always string[] (even for single-select).

import { useState } from 'react';
import { FlintSelect } from '@getufy/flint-ui-react/select';

function ControlledSelect() {
  const [value, setValue] = useState<string[]>([]);

  return (
    <FlintSelect
      label="Fruit"
      options={[
        { label: 'Apple', value: 'apple' },
        { label: 'Banana', value: 'banana' },
      ]}
      value={value}
      onFlintSelectChange={(e) => setValue(e.detail.value)}
    />
  );
}

Tabs

onFlintTabChange fires when the active tab changes. e.detail.value is the value string of the selected tab.

import { useState } from 'react';
import { FlintTabs, FlintTab, FlintTabPanel } from '@getufy/flint-ui-react/tabs';

function ControlledTabs() {
  const [tab, setTab] = useState('one');

  return (
    <FlintTabs value={tab} onFlintTabChange={(e) => setTab(e.detail.value)}>
      <FlintTab value="one">Tab 1</FlintTab>
      <FlintTab value="two">Tab 2</FlintTab>
      <FlintTabPanel value="one">Content 1</FlintTabPanel>
      <FlintTabPanel value="two">Content 2</FlintTabPanel>
    </FlintTabs>
  );
}

Recipes

Login form

import { FlintInput } from '@getufy/flint-ui-react/input';
import { FlintCheckbox } from '@getufy/flint-ui-react/checkbox';
import { FlintButton } from '@getufy/flint-ui-react/button';
import { FlintStack } from '@getufy/flint-ui-react/stack';

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  return (
    <form onSubmit={(e) => { e.preventDefault(); /* handle login */ }}>
      <FlintStack spacing={2}>
        <FlintInput
          label="Email"
          type="email"
          required
          value={email}
          onFlintInputChange={(e) => setEmail(e.detail.value)}
        />
        <FlintInput
          label="Password"
          type="password"
          required
          value={password}
          onFlintInputChange={(e) => setPassword(e.detail.value)}
        />
        <FlintCheckbox label="Remember me" />
        <FlintButton type="submit">Sign in</FlintButton>
      </FlintStack>
    </form>
  );
}

Card layout

import { FlintCard } from '@getufy/flint-ui-react/card';
import { FlintGrid, FlintGridItem } from '@getufy/flint-ui-react/grid';
import { FlintButton } from '@getufy/flint-ui-react/button';
import { FlintChip } from '@getufy/flint-ui-react/chip';

function CardGrid({ items }) {
  return (
    <FlintGrid columns={3} spacing={2}>
      {items.map((item) => (
        <FlintGridItem key={item.id}>
          <FlintCard>
            <div style={{ padding: '1rem' }}>
              <h3>{item.title}</h3>
              <p>{item.description}</p>
              <FlintChip variant="outlined">{item.category}</FlintChip>
              <FlintButton variant="secondary">View</FlintButton>
            </div>
          </FlintCard>
        </FlintGridItem>
      ))}
    </FlintGrid>
  );
}

Confirmation dialog

import { FlintDialog } from '@getufy/flint-ui-react/dialog';
import { FlintButton } from '@getufy/flint-ui-react/button';
import { FlintStack } from '@getufy/flint-ui-react/stack';

function ConfirmDialog({ open, onConfirm, onCancel }) {
  return (
    <FlintDialog open={open} onFlintDialogClose={onCancel}>
      <flint-dialog-title>Delete item?</flint-dialog-title>
      <p>This action cannot be undone.</p>
      <FlintStack direction="row" spacing={1} justifyContent="flex-end">
        <FlintButton variant="secondary" onClick={onCancel}>Cancel</FlintButton>
        <FlintButton variant="destructive" onClick={onConfirm}>Delete</FlintButton>
      </FlintStack>
    </FlintDialog>
  );
}

Toast notifications

Flint UI includes a Sonner-inspired toast system with an imperative toast() API:

import { FlintToaster, toast } from '@getufy/flint-ui-react/sonner';

function App() {
  return (
    <>
      {/* Place once at app root */}
      <FlintToaster position="bottom-right" />

      <button onClick={() => toast('Hello!')}>Show toast</button>
      <button onClick={() => toast.success('Saved!')}>Success</button>
      <button onClick={() => toast.error('Failed')}>Error</button>
      <button onClick={() => toast.promise(
        fetch('/api/save'),
        { loading: 'Saving...', success: 'Done!', error: 'Failed' }
      )}>Async</button>
    </>
  );
}

Available methods: toast(), toast.success(), toast.error(), toast.info(), toast.warning(), toast.loading(), toast.promise(), toast.dismiss().

AppBar with slots

Web Component slots are used via the slot attribute in React:

import { FlintAppBar } from '@getufy/flint-ui-react/app-bar';
import { FlintButton } from '@getufy/flint-ui-react/button';

function Header() {
  return (
    <FlintAppBar>
      <div slot="navigation"><FlintButton variant="text">☰</FlintButton></div>
      <div slot="title">My App</div>
      <div slot="actions"><FlintButton variant="text">⚙</FlintButton></div>
    </FlintAppBar>
  );
}

Available slots: navigation (or start-content), title, actions (or end-content), and the default slot.

Custom Elements Manifest

The core package ships a Custom Elements Manifest at dist/custom-elements.json. This machine-readable JSON describes all components, their properties, events, slots, and CSS custom properties.

// package.json
{ "customElements": "dist/custom-elements.json" }

Tooling that consumes CEM:

  • VS Code — autocompletion via dist/vscode.html-custom-data.json (add to html.customData setting)
  • JetBrains — autocompletion via dist/web-types.json (auto-detected)
  • Storybook — auto-generates ArgTypes from CEM
  • API docs — generated from CEM via npm run gen:docs

SSR / Next.js

Flint UI components work in server-rendered frameworks with some caveats. See the full SSR guide and Next.js guide for details.

Next.js App Router

All Flint React wrappers must be used in Client Components. Add 'use client' to any file that imports them:

'use client';

import { FlintButton } from '@getufy/flint-ui-react/button';

export function MyButton() {
  return <FlintButton>Click me</FlintButton>;
}

Client-only wrapper component

Create a reusable client boundary file for Flint components used across your app:

// components/flint-client.tsx
'use client';

export { FlintButton } from '@getufy/flint-ui-react/button';
export { FlintInput } from '@getufy/flint-ui-react/input';
export { FlintSelect, FlintOption } from '@getufy/flint-ui-react/select';
// re-export only the components you use

Then import from this file in any Server or Client Component:

import { FlintButton } from '@/components/flint-client';

Dynamic imports with ssr: false

For components that must not execute during SSR at all (e.g., they access window in module scope), use next/dynamic:

import dynamic from 'next/dynamic';

const FlintCarousel = dynamic(
  () => import('@getufy/flint-ui-react/carousel').then((mod) => mod.FlintCarousel),
  { ssr: false, loading: () => <div style={{ height: 300 }} /> }
);

export default function Page() {
  return <FlintCarousel>{/* slides */}</FlintCarousel>;
}

Theme CSS in layout

Import theme CSS in your root layout (this is safe in Server Components):

// app/layout.tsx
import '@getufy/flint-ui/theme.css';
import '@getufy/flint-ui/theme-dark.css'; // optional

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

Astro

Use the client:only directive to render Flint components exclusively on the client. For the React wrappers, use client:only="react":

---
import { FlintButton } from '@getufy/flint-ui-react/button';
---
<FlintButton client:only="react">Click me</FlintButton>

For vanilla web components without React, use a client-side <script> import:

<flint-button>Click me</flint-button>

<script>
  import '@getufy/flint-ui/button/flint-button';
</script>

Remix

Use a ClientOnly wrapper (from remix-utils) to prevent server-side rendering of Flint components:

import { ClientOnly } from 'remix-utils/client-only';
import { FlintButton } from '@getufy/flint-ui-react/button';

export default function Page() {
  return (
    <ClientOnly fallback={<button>Click me</button>}>
      {() => <FlintButton>Click me</FlintButton>}
    </ClientOnly>
  );
}

Known limitations

  • Hydration mismatch: Web components render as empty custom element tags during SSR; content appears after hydration. Use dynamic(..., { ssr: false }) or a loading skeleton to avoid layout shift.
  • window / document access: Not available during SSR. All Flint components guard DOM access in connectedCallback(), so importing them server-side is safe — but do not call DOM methods at the module level.
  • Custom element registration timing: customElements.define() runs when a component module is first imported. On the server, this is a no-op (the registry does not exist). On the client, ensure component modules are imported before the component tag appears in the DOM.
  • Declarative Shadow DOM (DSD): Lit SSR + DSD is experimental (~96% browser support). Flint UI does not yet ship pre-rendered DSD templates; components hydrate from empty shells.
  • suppress-warnings import: This is a no-op on the server (uses globalThis).
  • Autoloader: Do not import @getufy/flint-ui/autoloader on the server — it requires MutationObserver and document.body.

Suppressing the Lit dev mode warning

In development, Lit prints a console warning about running in dev mode. To silence it, add this import before any component imports in your app entry point:

// main.tsx — must be first
import '@getufy/flint-ui/suppress-warnings';
import '@getufy/flint-ui/theme.css';

// component imports after...

License

MIT