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

@mixedpplparty/juicer-m3

v1.1.12

Published

Accessible Material 3 Expressive React components built with Base UI.

Readme

juicer-m3

Accessible Material 3 Expressive components for React 18 and 19, built with Base UI and Motion. The visual system follows the m3 juicer Figma library and is implemented with application-overridable CSS custom properties.

Install

pnpm add juicer-m3

React and React DOM are peer dependencies. Load the complete stylesheet once:

import "juicer-m3/styles.css";
import { Button, ThemeProvider } from "juicer-m3";

export function App() {
  return (
    <ThemeProvider theme="light">
      <Button variant="filled">Continue</Button>
    </ThemeProvider>
  );
}

styles.css imports tokens.css and components.css. Applications that need explicit control may load the two files separately, with tokens first. Package declarations live in low-priority jm3.tokens and jm3.components cascade layers. Normal unlayered application CSS therefore wins without !important.

Theme overrides

All component colors, state layers, focus rings, icons, disabled states, error states, and elevations resolve through CSS variables. ThemeProvider uses local scope by default, preserving the existing subtree behavior and routing portaled components into the themed subtree:

<ThemeProvider theme="teal-dark">
  <AdminArea />
</ThemeProvider>

Use document scope when the theme should also cover global styles and portals mounted under document.body:

<ThemeProvider scope="document" theme="dark">
  <App />
</ThemeProvider>

The "system" preference resolves to the base light or dark theme and updates when the operating system preference changes:

<ThemeProvider scope="document" theme="system">
  <App />
</ThemeProvider>

Server rendering resolves "system" to light until the client preference is available. Apps that require a no-flash first paint should set data-jm3-theme before hydration.

The typed tokens property overrides semantic variables for one provider. In document scope the same overrides are synchronized to the document root and restored when the provider unmounts:

<ThemeProvider theme="dark" tokens={{ primary: "rebeccapurple" }}>
  <AdminArea />
</ThemeProvider>

An explicit Select portalProps.container takes precedence over the provider's local portal container.

CSS-only theming works by setting the same attribute:

<main data-jm3-theme="teal-dark">...</main>

The 32 modes are light/dark/high-contrast/medium-contrast, monochrome light/dark, and light/dark variants for pink, rose, red, orange, yellow, chartreuse, green, teal, cyan, blue, indigo, and purple. The warm light mode is the default. Every semantic color is an exact resolved value from the M3 variable collection in the m3-juicer Figma file; the package does not approximate palette modes by mixing seed colors.

The checked-in Figma snapshot is scripts/m3-colors.figma.json. After intentionally refreshing that snapshot from the M3 collection, regenerate and verify the CSS with:

pnpm generate:colors
pnpm check:tokens

Fonts

The default typeface variables reference IBM Plex Sans KR, but the package does not fetch or bundle fonts. Load the fonts in your application, or replace the variables:

:root {
  --md-ref-typeface-brand: "Your Brand Font", system-ui, sans-serif;
  --md-ref-typeface-plain: "Your UI Font", system-ui, sans-serif;
}

Typography

Text implements the complete m3 juicer type scale: display, headline, title, body, and label roles in large, medium, and small sizes, with optional emphasized styles. Font size, line height, weight, family, and tracking are application-overridable CSS variables. All text sizes use rem.

The visual type role is intentionally named typeRole so the native ARIA role attribute remains available. Text renders a neutral span by default because a visual Material role cannot determine the surrounding document's heading hierarchy:

import { Text } from "juicer-m3/text";

<Text as="h1" typeRole="display" size="large">
  Account overview
</Text>

<Text as="h2" typeRole="headline" size="medium" emphasized>
  Recent activity
</Text>

<Text as="p" typeRole="body" size="large">
  Activity from the last 30 days.
</Text>

Choose h1 through h6 according to the document outline, not the visual size. Avoid skipping heading levels. Use as="p" for standalone paragraphs and the default span for text embedded inside another component. The visual label role does not imply an HTML label; use a real label only when it is associated with a form control. Likewise, emphasized changes the visual type tokens but does not add semantic stress emphasis.

The root export is also available:

import { Text } from "juicer-m3";

Direct imports and icons

Every component family and icon has a tree-shakeable subpath:

import { Button } from "juicer-m3/button";
import { SearchIcon } from "juicer-m3/icons/search";
import "juicer-m3/styles.css";

Icon components use normalized Material Symbols Rounded paths, inherit currentColor, are hidden from assistive technology by default, and become named images when given a title. Icon-only controls require an aria-label.

Select

Select is a compound, Base UI-backed control with outlined and filled Material variants. Its label is associated with the trigger automatically, and keyboard navigation, typeahead, form submission, focus management, disabled options, groups, portals, and dismissal follow Base UI's accessible Select behavior:

import { Select } from "juicer-m3/select";

const fruits = [
  { label: "Apple", value: "apple" },
  { label: "Orange", value: "orange" },
];

<Select.Root defaultValue="orange" items={fruits} name="fruit">
  <Select.Label>Fruit</Select.Label>
  <Select.Trigger>
    <Select.Value placeholder="Choose a fruit" />
    <Select.Icon />
  </Select.Trigger>
  <Select.Description>Choose one option.</Select.Description>
  <Select.Popup>
    <Select.List>
      {fruits.map((fruit) => (
        <Select.Item key={fruit.value} value={fruit.value}>
          <Select.ItemIndicator />
          <Select.ItemText>{fruit.label}</Select.ItemText>
        </Select.Item>
      ))}
    </Select.List>
  </Select.Popup>
</Select.Root>;

Set variant="filled" on Select.Root for the filled text-field treatment. Pass validation state through fieldProps, for example fieldProps={{ invalid: true }}, and render Select.Error for the associated error message. Select.Group, Select.GroupLabel, Select.Separator, and the scroll-arrow parts support longer structured menus. For large or filterable datasets, use a combobox instead of Select so people can search the available values. className and CSS-in-JS generated classes on Select.Root are applied to the .jm3-select-field wrapper, so styles such as inline-size: 100% control the complete field.

Skeleton

Skeleton follows the shadcn Base UI component contract: it renders a plain <div> with data-slot="skeleton", merges className, forwards native div props, and leaves its dimensions to application classes or styles.

import { Skeleton } from "juicer-m3/skeleton";

<Skeleton className="profile-placeholder" />;
.profile-placeholder {
  inline-size: 6.25rem;
  block-size: 1.25rem;
  border-radius: var(--md-sys-shape-corner-full);
}

Its fill and default radius use Material CSS variables, and its pulse animation is disabled when the user requests reduced motion. Skeletons are visual placeholders rather than progress announcements. Put aria-busy="true" and an accessible name on the loading region when status must be communicated, and mark decorative skeleton shapes aria-hidden="true".

Avatar

Avatar follows the shadcn Base UI API, including image fallback behavior, badges, groups, group counts, and sm, default, and lg sizes:

import {
  Avatar,
  AvatarBadge,
  AvatarFallback,
  AvatarGroup,
  AvatarGroupCount,
  AvatarImage,
} from "juicer-m3/avatar";

<AvatarGroup aria-label="Project members">
  <Avatar>
    <AvatarImage alt="Chris Nguyen" src="/avatars/chris.jpg" />
    <AvatarFallback>CN</AvatarFallback>
    <AvatarBadge aria-label="Online" />
  </Avatar>
  <Avatar>
    <AvatarFallback>LR</AvatarFallback>
  </Avatar>
  <AvatarGroupCount aria-label="3 more members">+3</AvatarGroupCount>
</AvatarGroup>;

Always include AvatarFallback, provide useful alt text for meaningful profile images, and label an empty AvatarBadge when it communicates status. Sizes, overlap, badges, and typography use rem-based values; colors, outlines, and rings use application-overridable Material CSS variables.

Role indicators

RoleIndicator pairs an application-supplied role color with a readable role name. The marker is decorative—the text carries the meaning—so role information remains available without relying on color alone. The active state adds the Figma-specified inset underline without changing layout.

import { RoleIndicator } from "juicer-m3/role-indicator";

<RoleIndicator
  active
  color="var(--app-role-admin)"
  roleName="Admin"
/>;

The label uses the shared M3 body-medium typography variables. The active underline uses --md-sys-color-surface-tint-opacity-16, and both it and the role color can be overridden with CSS variables.

Composable list items

ListItem renders a semantic <li> by default. Use its Base UI-style render prop when the whole visual row should be an anchor or a routing-library link:

import { List, ListItem } from "juicer-m3/list";

<List>
  <ListItem
    headline="Settings"
    supportingText="Preferences and account"
    render={<a href="/settings" />}
  />
</List>;

The rendered link remains inside an <li>, preserving valid list semantics. Event handlers, classes, refs, ARIA attributes, content, and selected or segmented state are merged into the rendered element. The callback form receives the merged element props and ListItemState.

Transparent app bars and lists

Use container="transparent" when an app bar or list should inherit the background of its surroundings:

import { AppBar, BottomAppBar } from "juicer-m3/app-bar";
import { List } from "juicer-m3/list";

<AppBar container="transparent" title="Dashboard" />;
<List container="transparent">{/* list items */}</List>;
<BottomAppBar container="transparent">{/* actions */}</BottomAppBar>;

The default value is container="default". A transparent app bar also suppresses elevation, including when elevated is present. Selected list items and hover, focus, and pressed state layers remain visible inside a transparent list.

SSR

Modules are ESM and safe to import during server rendering. Interactive entry points carry "use client"; presentational modules do not. In React Server Component applications, place interactive controls below a client boundary. Load CSS from the application shell to avoid a flash of unstyled content.

Local package distribution with pnpm

The package can be installed directly from a generated tarball; publishing to npm is not required. A tarball is preferable to pnpm link when validating a release because it contains exactly the files that a published package would contain and exercises the declared package exports.

From this repository, install dependencies and create the package:

cd C:\code-repos\juicer-m3
pnpm install
pnpm pack

The prepack script builds the ESM modules and declarations and validates the package exports. The resulting file is written to the repository root and includes the package version in its name, for example:

C:\code-repos\juicer-m3\juicer-m3-0.1.3.tgz

To place the exported package in a different directory, pass an explicit destination:

pnpm pack --pack-destination "C:\local-packages"

In the project that will consume the library, install the tarball by its path:

cd C:\code-repos\my-react-app
pnpm add "C:\code-repos\juicer-m3\juicer-m3-0.1.3.tgz"

The tarball can also be copied into the consuming repository—for example, into a vendor directory—and installed with a relative path:

pnpm add ".\vendor\juicer-m3-0.1.3.tgz"

pnpm records the local tarball as a file: dependency in the consuming project's package.json and lockfile. The consuming application uses the package exactly like an npm-hosted package:

import { Button, ThemeProvider } from "juicer-m3";
import { SearchIcon } from "juicer-m3/icons/search";
import "juicer-m3/styles.css";

React and React DOM remain peer dependencies and should already be dependencies of the consuming application. The application is also responsible for loading IBM Plex Sans KR or overriding the font variables.

After changing juicer-m3, run pnpm pack again. Prefer incrementing the package version before sharing a new artifact so its filename and lockfile entry are unambiguous. For quick local iteration with the same version, reinstall the rebuilt tarball with pnpm add --force <path-to-tarball>. Treat copied tarballs as immutable build artifacts and do not edit their contents manually.

Development

pnpm storybook
pnpm test
pnpm test:e2e
pnpm check
pnpm pack

Biome is the only formatter and linter. Vitest covers semantics, Base UI interaction, form state, focus restoration, theming, SSR, and color-token invariants. Playwright targets Chromium, Firefox, and WebKit. pnpm pack writes a publication-ready tarball to the repository root.

License

MIT. Material Symbols are licensed under Apache-2.0; see THIRD_PARTY_NOTICES.md.