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

@fieldui/react

v0.4.0

Published

React audio components for Field — waveforms, transports, meters, spectrograms, scrubbers, regions, knobs, faders. Built on @fieldui/core hooks and @fieldui/tokens.

Readme

@fieldui/react

Visual React components for Field, a headless-first React component library for audio interfaces (waveforms, transports, meters, spectrograms, scrubbers, regions, knobs, faders).

This package is the paint layer. Every component reads tokens from @fieldui/tokens for styling and consumes hooks from @fieldui/core for behaviour. If you want a custom UI on the same audio engine, drop this package and build directly on @fieldui/core.

┌──────────────────────────────────────────────────────────────┐
│  @fieldui/react   ←    components (canvas + radix + tailwind) │
│  @fieldui/core         headless hooks (audio graph, RAF, etc.)│
│  @fieldui/tokens       CSS variables + TS token map           │
└──────────────────────────────────────────────────────────────┘

Highlights

  • Canvas where speed matters. Waveforms, meters, spectrograms, and timelines paint to canvas at device pixel ratio. Sizing happens in effects (not RAF) so the bitmap is always correct. Colours are resolved once per resize via getComputedStyle and cached for the hot loop.
  • Controlled or hook-driven. Anything that animates accepts both styles. Pass currentTime for SSR or static rendering, or timeRef + duration + playing to drive the visual imperatively from a RAF loop with zero React re-renders during playback. The unplayed-dim on the waveform tracks the playhead frame-perfectly this way.
  • Compound transport. <Transport.Root> carries state and callbacks via context. .Play, .Pause, .Stop, .SkipBack, .SkipForward, and .Record are slot-style children. Pick which buttons you mount, in what order.
  • Customisation paths, in order of invasiveness. ① override CSS variables on :root for theming. ② className passthrough for per-component tweaks (every component runs through cn() so user classes win). ③ compound slots (Transport, MediaButton). ④ drop to @fieldui/core and write your own markup.
  • Accessibility baked in. Knobs and faders expose proper ARIA (role="slider" with aria-valuemin/max/now), level meters use role="meter", waveforms role="img" with a label prop, and the transport toolbar carries role="toolbar". Keyboard handling (arrows / shift fine / page / home / end) on every continuous control.

Install

npm install @fieldui/react @fieldui/core @fieldui/tokens react react-dom
# or pnpm / yarn / bun

@fieldui/core and @fieldui/tokens are runtime deps of this package and will be installed automatically. Listing them above only matters because their public APIs surface in your code (you import providers from @fieldui/core, override variables defined by @fieldui/tokens).

Then import the styles once at your app entry:

import "@fieldui/react/styles.css";

This includes @fieldui/tokens/tokens.css plus a thin field-preset.css layer for body font, paper background, .field-tabular, and .field-bitmap helpers.

Compatible with Tailwind v4, but does not require it. The styles.css shipped in dist/ is pre-compiled — every utility class the components emit (h-10, min-h-20, the MediaButton size variants, etc.) is already baked in, alongside the design tokens and base styles. You don't need Tailwind installed, no @source config in your app, and no node_modules scanning quirks: @import "@fieldui/react/styles.css" is genuinely all you need.

If you DO use Tailwind v4 in your app and want to compose with your own utility layer (and tree-shake against your app's own usage), import the source preset instead:

@import "tailwindcss";
@import "@fieldui/react/field-preset.css";
@source "../node_modules/@fieldui/react/dist";

The @source line is required because Tailwind v4 excludes node_modules from auto-scanning even when the imported CSS declares its own @source. (This is why the pre-compiled styles.css exists — so most consumers don't have to know this.)


Fonts

field-preset.css loads DM Sans, JetBrains Mono, and VT323 from Google Fonts via an @import url(...). This works out of the box but bypasses Next.js's font optimization. For production Next.js apps, prefer next/font/google and override the font CSS variables yourself:

// app/layout.tsx
import { DM_Sans, JetBrains_Mono, VT323 } from "next/font/google";

const sans = DM_Sans({ subsets: ["latin"], variable: "--font-sans-next" });
const mono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-mono-next" });
const lcd = VT323({ weight: "400", subsets: ["latin"], variable: "--font-lcd-next" });

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html className={`${sans.variable} ${mono.variable} ${lcd.variable}`}>
      <body>{children}</body>
    </html>
  );
}
/* app/globals.css */
:root {
  --font-family: var(--font-sans-next);
  --font-mono-family: var(--font-mono-next);
  --font-bitmap: var(--font-lcd-next);
}

The Google Fonts CDN load in field-preset.css becomes a no-op (the browser caches it but your overrides win) or you can opt out entirely by copying field-preset.css into your app and removing the @import url.


Quickstart

A complete one-file player with providers, decode, transport, scrubber, playhead, waveform, and time readout:

import "@fieldui/react/styles.css";

import {
  AudioGraphProvider,
  RenderLoopProvider,
  useAudioBuffer,
  useAudioGraph,
  useAudioSource,
  usePlaybackTime,
  useTransport,
} from "@fieldui/core";
import {
  PlayheadMarker,
  Scrubber,
  TimeDisplay,
  Timeline,
  Transport,
  WaveformDisplay,
} from "@fieldui/react";

const App = () => (
  <RenderLoopProvider>
    <AudioGraphProvider>
      <Player src="/clip.wav" />
    </AudioGraphProvider>
  </RenderLoopProvider>
);

const Player = ({ src }: { src: string }) => {
  const { masterAnalyser } = useAudioGraph();
  const { buffer } = useAudioBuffer(src);
  const audioSource = useAudioSource(buffer ? { buffer } : null);

  const transport = useTransport({
    buffer,
    createBufferSource: audioSource.createBufferSource,
    connectSource: (node) => masterAnalyser && node.connect(masterAnalyser),
  });
  const time = usePlaybackTime(transport);
  const duration = transport.duration;

  return (
    <section className="space-y-4 p-6">
      <header className="flex items-baseline justify-between">
        <h1 className="text-2xl font-semibold">Audio player</h1>
        <TimeDisplay seconds={transport.currentTime} format="mm:ss.ms" />
      </header>

      <div className="relative">
        <WaveformDisplay
          buffer={buffer}
          timeRef={time.timeRef}
          duration={duration}
          playing={transport.state === "playing"}
          progress={duration > 0 ? transport.currentTime / duration : 0}
          className="h-32"
        />
        <PlayheadMarker
          timeRef={time.timeRef}
          duration={duration}
          playing={transport.state === "playing"}
          position={duration > 0 ? transport.currentTime / duration : 0}
        />
      </div>

      <Timeline
        duration={duration}
        currentTime={transport.currentTime}
        timeRef={time.timeRef}
        playing={transport.state === "playing"}
      />

      <div className="flex items-center gap-4">
        <Transport
          state={transport.state}
          onPlay={transport.play}
          onPause={transport.pause}
          onStop={transport.stop}
          onSkip={(d) => transport.seek(transport.getCurrentTime() + d)}
        >
          <Transport.SkipBack />
          <Transport.Play />
          <Transport.Pause />
          <Transport.Stop />
          <Transport.SkipForward />
        </Transport>
        <div className="flex-1">
          <Scrubber
            value={transport.currentTime}
            max={Math.max(duration, 0.0001)}
            onChange={transport.seek}
          />
        </div>
      </div>
    </section>
  );
};

Component surface

| Family | Components | |--------------|------------------------------------------------------------| | button | MediaButton. Flat-prop API (icon="play", label, badge="on") for the common case, plus the compound .Root + .Icon + .Label + .Badge slots for full control. Square or rectangle, three sizes, three tones. Built-in icons: play, pause, stop, forward, back, record (the record dot pulses on pressed, matching Transport.Record). | | transport | Transport (compound: .Root, .Play, .Pause, .Stop, .SkipBack, .SkipForward, .Record) | | waveform | WaveformDisplay. Peaks, peaksUrl, or buffer; signed envelope; RAF-driven progress overlay. density prop (comfortable / compact / thin) controls the min-height floor — set thin to let a smaller className="h-2" actually take effect. | | scrubber | Scrubber. Radix Slider underneath, brand-styled. | | timeline | Timeline. Nice-interval ticks, controlled or RAF-driven playhead. | | meter | LevelMeter. Segmented LED bars, controlled or analyser-driven. | | controls | Knob (rotary), Fader (linear or log taper) | | display | TimeDisplay. LCD-style; formats mm:ss, mm:ss.ms, hh:mm:ss, SMPTE, bars:beats, samples. | | region | RegionSelector (drag/resize via useRegionDrag), PlayheadMarker | | spectrogram| Spectrogram. Scrolling FFT, mono or fire colour map. | | layout | Grid, GridItem. 4/8/16-col responsive scaffold. |

Every visual component:

  • Forwards className and arbitrary HTML props through ...rest.
  • Reads colour from CSS variables, so the brand override pattern in @fieldui/tokens just works.
  • Is DPR-aware on canvas. Sharp on retina, no blurry bitmap on resize.

Theming

There's no ThemeProvider. Re-declare the variables on :root (or any ancestor) and the change cascades through every component:

:root[data-theme="lab"] {
  --color-audio-scrubber:    var(--color-cobalt);
  --color-audio-region:      color-mix(in oklab, var(--color-cobalt) 18%, transparent);
  --color-audio-meter-warn:  var(--color-ochre);
  --color-button-accent:     var(--color-cobalt);
}

Toggle <html data-theme="lab"> on a media-query, a user setting, or a Storybook toggle, and rust shifts to cobalt across every meter, scrubber, and button. No React state involved.

See @fieldui/tokens for the full vocabulary.


Sibling packages

| Package | Role | |---------|------| | @fieldui/core | Headless React hooks for audio: transport, audio graph, peaks, regions, recording, analysis, export. Every component here is a paint layer over these hooks. | | @fieldui/tokens | Design tokens. CSS variables and TypeScript token map. Brand palette, typography stack, spacing/radius scales, audio-semantic colours, Tailwind anchor overrides. |


Compatibility

  • React 18 or 19 (peer dep on both).
  • Tailwind v4 for the prebuilt class strings.
  • Modern evergreen browsers. Uses AudioContext, MediaRecorder, ResizeObserver, Pointer Events, and matchMedia. ES2022 / Node ≥ 20 for development.

License

MIT. © 2026 fieldui.