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

@swisper.ai/ui

v0.4.5

Published

React components of Echo DS, the Swisper design system

Readme

@swisper.ai/ui

npm license

React components of Echo DS, the Swisper design system. They are styled with Tailwind v4 classes against the Echo theme, and the package ships them as one prebuilt stylesheet that the app loads once. A few parts render MUI (Link, ChipsField, the bundled icons), so the MUI and Emotion peers are required even if you never use those parts.

Boundary

Code belongs here if it is a product-agnostic React component or hook that any Swisper app could use, styled only with Echo tokens. Chat surfaces belong in @swisper.ai/web-chat-ui, file previews in @swisper.ai/web-file-preview, maps in @swisper.ai/web-map, icons in @swisper.ai/icons, tokens and the Tailwind theme in @swisper.ai/design-system, and unstyled React helpers in @swisper.ai/web-core.

Install

npm install @swisper.ai/ui

| Peer | Range | Why | | --------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------- | | react | >=18.0.0 | Components and hooks | | react-dom | >=18.0.0 | Rendering, and the portals that dialogs, menus, popovers and tooltips open into | | @mui/material | >=7.0.0 | Link, the bundled icons (SvgIcon) and useIsResolution (useMediaQuery). The package imports it as soon as it is loaded | | @mui/icons-material | >=7.0.0 | The remove icon on ChipsField chips | | @mui/system | >=7.0.0 | MUI's styling system; keep it on the same major as @mui/material | | @emotion/react | ^11.0.0 | MUI's style engine | | @emotion/styled | ^11.0.0 | MUI's style engine |

@swisper.ai/icons is bundled into dist/, so you don't install it; @swisper.ai/web-core, the Radix UI primitives, motion, react-hook-form and zod are ordinary dependencies that npm installs with the package.

Setup

  1. Load the styles once, in your app's entry file, before your own CSS:

    import '@swisper.ai/ui/index.css';
  2. If you use tooltips, wrap the app in TooltipProvider. Radix requires it, and it sets the Echo default of no hover delay.

  3. Optional: the MUI parts (Link, ChipsField) take their colors from the app's MUI theme. For the Echo look, install @swisper.ai/design-system and wrap the app in MUI's ThemeProvider with darkTheme or lightTheme from @swisper.ai/design-system/mui-theme.

  4. Render a component:

import '@swisper.ai/ui/index.css';

import { Button, Tooltip, TooltipProvider } from '@swisper.ai/ui';
import { createRoot } from 'react-dom/client';

function App() {
  return (
    <TooltipProvider>
      <Tooltip tooltip="Saves the draft without sending it">
        <Button>Save draft</Button>
      </Tooltip>
    </TooltipProvider>
  );
}

createRoot(document.getElementById('root')!).render(<App />);

Styles

@swisper.ai/ui/index.css is a complete Tailwind v4 build: Tailwind's preflight (base reset), the Echo theme as CSS variables (colors, typography, radii, the --breakpoint-* variables the resolution hooks read) and the utilities the components use. Import it once, at the app entry. Loaded after your own stylesheet, its preflight overrides your base styles (fonts, headings, resets); loaded twice, every rule is duplicated and whichever copy loads last wins.

The theme changes Tailwind's spacing scale: a number in a spacing class is pixels, so gap-8 is 8px, not Tailwind's default 32px. That holds for any class you write with this theme too.

With your own Tailwind v4 build

If your app compiles its own Tailwind, build the Echo classes into it instead of loading index.css next to it. Install @swisper.ai/design-system and tw-animate-css (the components' enter and exit animations), then in your Tailwind entry:

@import 'tw-animate-css';
@import '@swisper.ai/design-system/src/tailwind.css';
@source '../node_modules/@swisper.ai/ui/src';

@swisper.ai/design-system/src/tailwind.css imports tailwindcss itself. The @source path is relative to the CSS file; point it at wherever node_modules sits.

API

Every component is exported from @swisper.ai/ui. Props, variants, defaults and live examples are in the public Storybook: https://fintama.github.io/fintama/.

Actions

| Export | Why it exists | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Button | The button for committing an action, with Echo variants, sizes, a loading state and leading/trailing icons | | IconButton | An icon-only button for compact and utility actions; give it an aria-label | | ButtonGroup | A single-select segmented control: styles the selected Button child as prominent and the rest as quiet | | ButtonSplit, ButtonSplitPrimaryButton, ButtonSplitActionButton | A labelled action joined to a chevron that opens its options, sharing one variant and size | | Link | Inline navigation; isExternal adds target="_blank" and a safe rel, startIcon puts an icon before the text, and hasUnderline={false} drops the hover underline | | Menu, MenuProvider, MenuTrigger, MenuPortal, DropdownMenuList, DropdownMenuListItem, MenuItemIcon, MenuItemLabel, MenuSeparator, MenuSub, MenuSubTrigger, MenuSubContent | A dropdown menu of actions opened from a trigger, with icons, selected and disabled items, and submenus |

Inputs and forms

| Export | Why it exists | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | TextField | A single-line input with the Echo outlined frame, label, helper text, error state and start/end icons | | TextArea | The multi-line counterpart of TextField | | CounterField | A whole number stepped with minus and plus buttons or typed (guests, rooms): TextField with a button at each end. Digits only, kept between min and max; onValueChange gets the number | | SelectField | A form select built from an options array, with label, helper text, error, loading and read-only states | | SelectRoot, SelectTrigger, SelectTriggerValue, SelectValue, SelectChevronIcon, SelectPortal, SelectContent, SelectViewport, SelectItem, SelectItemIcon, SelectGroup, SelectGroupLabel, SelectLabel, SelectSeparator, useSelectContext, selectTriggerVariants, selectOutlineVariants, selectChevronIconVariants, selectFieldLegendVariants | The low-level select parts, for a trigger or option layout SelectField can't express | | ComboBox, ComboBoxTrigger, ComboBoxLabel, ComboBoxValue, ComboBoxChevronIcon, ComboBoxContent, ComboBoxInput, ComboBoxList, ComboBoxItem, ComboBoxEmpty, ComboBoxGroup, ComboBoxSeparator, comboBoxItemVariants, useComboBoxContext | A searchable single-select for 8 or more options (countries, currencies): a select-like trigger opens a popover with a search field over the list | | Checkbox, CheckboxField | A checkbox with checked, unchecked and indeterminate states; CheckboxField adds a clickable label | | Switch, SwitchField | An on/off toggle for settings that apply immediately; SwitchField adds a label | | RadioGroup, RadioGroupItem, RadioGroupCheckbox, RadioGroupLabel | A controlled group of mutually exclusive options | | ChipsField | A tag input: the user types a value and presses Enter to add it as a chip, with duplicate detection and custom validation |

Overlays

| Export | Why it exists | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Dialog, DialogRoot, DialogTrigger, DialogPortal, DialogOverlay, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogClose, DialogFooter | A modal for a focused task or decision, with focus trap, overlay and header, body and footer slots | | Sheet, SheetTrigger, SheetContent, SheetOverlay, SheetHeader, SheetTitle, SheetClose, SheetBody, SheetDescription, SheetFooter, SheetPortal, sheetContentVariants | A modal panel attached to an edge of the viewport (bottom by default) for a secondary task that keeps the page behind an overlay | | Popover, PopoverTrigger, PopoverContent | A floating panel opened by click that can hold interactive content | | HoverCard, HoverCardTrigger, HoverCardContent | A read-only card that opens on hover or keyboard focus | | Tooltip, TooltipProvider, TooltipRoot, TooltipTrigger, TooltipContent, withTooltip | A short, non-interactive label on hover and focus. Tooltip composes the parts; TooltipProvider must wrap them; withTooltip is a Storybook decorator that adds the provider | | Backdrop | A full-screen overlay behind a modal surface, optionally mobile-only or click-to-dismiss |

Navigation

| Export | Why it exists | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | Tabs, TabsList, TabsTrigger, TabsContent | Switches between panels, horizontally or vertically, with icons, closable tabs and error marks | | MenuList, MenuListItem, MenuListButton, MenuListAction, MenuListItemIcon, MenuListItemLabel, MenuListItemBadge, MenuListItemCaption, MenuListDivider, MenuListSubmenu, MenuListSubmenuButton, MenuListSubmenuItem | The ready-styled menu built on List: sidebar and settings navigation, recent chats, collapsible submenus | | Stepper, StepperList, Step, StepperTrigger, useStepper, useStepperHook | Progress through a multi-step flow. useStepperHook holds the current step; useStepper reads it in parts |

Layout and surfaces

| Export | Why it exists | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Card, CardHeader, CardHeaderContent, CardIcon, CardTitle, CardDescription, CardAction, CardContent, CardFooter | Groups related content and actions into one unit with fixed slots | | PanelProvider, Panel, PanelHeader, PanelTitle, PanelActions, PanelActionButton, PanelActionCollapseButton, PanelActionExpandButton, PanelActionMoreButton, PanelActionCloseButton, PanelContent, PanelBody, PanelFooter, usePanel, panelVariants, panelBodyPadding | A non-modal region on one edge of a layout (right by default) with a fixed header, a scrolling body and an optional footer; it collapses to a rail showing only its title. usePanel collapses and expands it from code | | Collapsible, CollapsibleTrigger, CollapsibleChevron, CollapsibleContent, collapsibleVariants, collapsibleTriggerVariants, collapsibleContentVariants | Shows and hides content behind a trigger; CollapsibleChevron is the decorative arrow that rotates when open (direction is vertical or horizontal; isExpanded drives it outside a Collapsible). The collapsible…Variants helpers build a className from your classes and your classes per state (expanded, collapsed, disabled) | | ContextualMenu, ContextualMenuHeader, ContextualMenuContent | A titled section whose header collapses its content | | ScrollArea, ScrollBar | A bounded scrolling region with a styled scrollbar that appears on hover | | LoginLayout, LoginLayoutContainer, LoginLayoutMarketing, LoginLayoutMarketingImage, LoginLayoutMarketingMovie, LoginLayoutContent, LoginLayoutContentHeader, LoginLayoutContentBody, LoginLayoutContentFooter | A full-viewport sign-in screen: a marketing media panel beside the form column, hidden on small screens | | Divider | A horizontal or vertical separator with role="separator" | | DisabledArea | Renders a region as disabled (dimmed, not clickable), or hides it |

Data display

| Export | Why it exists | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Table, TableContainer, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption | A semantic table with row density and zebra striping set once on the root | | TableToolbar, TableToolbarLeft, TableToolbarActions, TableSearchButton, TableSearchTextField | The bar above a table, holding search and actions | | TablePagination, TablePaginationRowsPerPage, TablePaginationRowsPerPageLabel, TablePaginationRowsPerPageSelect, TablePaginationRowsPages, TablePaginationNavigation, TablePaginationPrevPageButton, TablePaginationNextPageButton | Rows-per-page and page navigation below a table | | List, ListItem, ListItemTrigger, ListItemTitle, ListItemDescription, ListItemAction, listItemVariants, ListSubHeader | An unstyled, keyboard-navigable list of rows you style yourself, with section labels; listItemVariants gives the reference look | | MetaList, MetaListItem, MetaListSeparator | One line of secondary metadata with bullet separators added for you (PDF • 2.4 MB • 7 Sep 2026) | | Timeline, TimelineItem, TimelineItemIcon, TimelineItemTimestamp, TimelineItemContent | Chronological events with timestamps and a connecting line | | Avatar, AvatarImage, AvatarFallback, AvatarBadge | A user or entity as photo, initials or icon, with an optional status badge | | Photo, preloadPhoto | A remote photo cropped to a box, on a neutral ground or a placeholder image until it loads. preloadPhoto(src) caches one ahead | | Chip | A compact label for a status, category or tag; static unless made interactive | | Typography | Text in the Echo type scale, rendering the matching element (h1–h6, p, span) for a variant | | Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext, useCarouselContext | A horizontally scrolling row of items with snapping and previous/next buttons. align="center" rests one item in the middle; initialSlide opens on a given item | | BaseCarousel, BaseCarouselViewport, BaseCarouselContent, BaseCarouselItem, BaseCarouselPrevious, BaseCarouselNext | The carousel's behaviour and context without its styling, for a carousel with its own layout | | SubscriptionPlans, SubscriptionPlanCard, SubscriptionPlanCardHeader, SubscriptionPlanCardTitle, SubscriptionPlanCardPrice, SubscriptionPlanCardAmount, SubscriptionPlanCardPeriod, SubscriptionPlanCardAction, SubscriptionPlanCardFeatures, SubscriptionPlanCardFeaturesIntro, SubscriptionPlanCardFeature, SubscriptionPlanCardWarning | A row of pricing cards for comparing subscription tiers |

Feedback

| Export | Why it exists | | ------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | Banner, BannerTitle, BannerDescription, BannerTimestamp, BannerAction, BannerCancel, BannerConfirm, BannerClose | An inline info, success, warning or error message, announced to screen readers when it appears | | EmptyState, EmptyStateIcon, EmptyStateTitle, EmptyStateDescription, EmptyStateActionRow | What to show where content doesn't exist yet: an empty list, a search with no results | | Skeleton | A placeholder shape for content that is still loading; size it with className to match what it stands in for | | LoadingIndicator | A rotating icon in a role="status" region for a short inline wait; label names what is loading | | Spinner, SOURCE_ANIMATION | A Lottie loading animation in four sizes, for a whole panel waiting for its first content. SOURCE_ANIMATION is the URL it loads |

Hooks

| Export | Why it exists | | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | useIsMobile, useIsResolution | Whether the viewport is in the Echo mobile, tablet or desktop range, read from the theme's breakpoints | | useCopyToClipboard | Copies text and reports success for a short time, so a copy button can show it worked | | useFullscreen | Enters and leaves fullscreen for one element, across browser prefixes | | useStep | A bounded step counter with next, previous and reset actions | | useCountdownAction, useRemainingSeconds | Runs an action, then disables it for a cooldown (30 s by default); useRemainingSeconds counts the seconds left |

Utilities

| Export | Why it exists | | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | cn | Deprecated: import cn from @swisper.ai/web-core instead. This one is an alias of it, kept so existing imports work, and will be removed in a future major | | buttonVariants, chipVariants, typographyVariants, tabsListVariants, tableCellVariants and the other *Variants | The components' class functions, for giving another element (a router link, say) a component's look | | getCssVariableValue, getVariableNumberValue | Read a numeric CSS variable (a theme size or breakpoint) from the page; they throw when it is missing or has another unit. Re-exported from @swisper.ai/web-core | | interactiveSurfaceBackground | The classes behind Button's hover and pressed backgrounds, a token layered over a 'filled' or 'overlay' surface. Set the --interactive-* variables beside it | | VisuallyHidden | Hides content on screen while keeping it for screen readers | | @swisper.ai/ui/index.css | The prebuilt stylesheet (see Styles) |

Troubleshooting

| Symptom | Cause | Fix | | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | "Invalid hook call", or a context is missing | The app resolves two copies of React | Add resolve: { dedupe: ['react', 'react-dom'] } to the Vite config | | Components render without layout or colors | The styles aren't loaded, and Tailwind never scans the package, because it skips node_modules by default | Import @swisper.ai/ui/index.css, or build the classes yourself (Styles, With your own Tailwind v4 build) | | Layout is right, but colors or radii are wrong or missing | In your own Tailwind build, the design-system theme isn't loaded, so theme utilities such as bg-paper-elevation-1 are empty | Import @swisper.ai/design-system/src/tailwind.css in your Tailwind entry | | Your own base styles (fonts, headings, resets) get overridden | The prebuilt index.css is loaded next to your own Tailwind build, and its preflight wins | Use one of the two: drop index.css and build the classes yourself (Styles) | | Spacing looks wrong after upgrading (huge gaps, tiny icons) | A number in a spacing class is pixels: gap-8 is 8px, not Tailwind's default 32px | Write the pixel value you want | | Styles are missing or doubled, or the MUI theme doesn't apply | The app resolves two copies of @emotion/react or @mui/material | Add them to resolve.dedupe in the Vite config, next to react and react-dom | | ERR_UNSUPPORTED_DIR_IMPORT when a test imports the package | dist/ uses extensionless imports that only a bundler resolves, and Vitest externalizes it | Set server.deps.inline: [/@swisper\.ai\//] in the Vitest config | | "Failed to resolve import '@mui/material'" (or @emotion/react) although you use no MUI parts | The package imports MUI when it loads, for Link, the icons and useIsResolution | Install every peer in the Install table | | "Tooltip must be used within TooltipProvider" | A tooltip renders outside a TooltipProvider | Wrap the app (or the subtree) in TooltipProvider | | useIsMobile throws "CSS variable --breakpoint-md not found" | The resolution hooks read --breakpoint-* from the page, and the stylesheet that defines them isn't loaded (or the test DOM has no CSS) | Load @swisper.ai/ui/index.css; in unit tests, mock the hook | | Link or ChipsField look like stock MUI | They render MUI components, which take the app's MUI theme | Wrap the app in MUI's ThemeProvider with a theme from @swisper.ai/design-system/mui-theme | | "ListItemText is not exported" (or ListItemIcon, isInteractive, dense) after upgrading | List became an unstyled primitive in 0.2; those styling parts were removed | Wrap each row's content in ListItemTrigger, use ListItemTitle / ListItemDescription for its text, and pass className={listItemVariants()} to ListItem for the previous look | | The Spinner is blank under a strict CSP or offline | Its animation loads from lottie.host at runtime | Allow https://lottie.host in connect-src, or use LoadingIndicator |

License

Apache-2.0