Skip to content

Repository files navigation

React Pixel UI

npm version bundle size license

Turn supported CSS into pixel art. Wrap one rendered HTML element with <Pixel> — Tailwind, inline styles, and CSS modules work without Canvas and remain SSR compatible.

Demo | npm | GitHub

React Pixel UI — turn CSS into pixel art

Why React Pixel UI?

  • Keep real DOM and CSS — text, layout, events, and accessibility stay on the original element.
  • Use existing styles — Tailwind, CSS modules, inline styles, gradients, borders, and shadows are read from computed CSS.
  • No Canvas dependency — pixel art is generated with clip-path, RGBA PNG data URLs, and drop-shadow().
  • SSR compatible — elements render normally on the server and are enhanced after hydration.
  • Reactive by default — style, class, theme, hover, focus, and active changes can be observed automatically.
  • Framework-independent core — use @react-pixel-ui/core when you only need the CSS-to-pixel-art engine.

Install

npm install @react-pixel-ui/react
# or
pnpm add @react-pixel-ui/react
# or
yarn add @react-pixel-ui/react

Requires React 18+. @react-pixel-ui/core is installed automatically.

Quick Start

import { Pixel } from "@react-pixel-ui/react";

function App() {
  return (
    <Pixel size={6}>
      <div
        style={{
          background: "linear-gradient(135deg, #ff6b6b, #4ecdc4)",
          borderRadius: 16,
          border: "3px solid #333",
          padding: 20,
        }}
      >
        Pixel Art!
      </div>
    </Pixel>
  );
}

That's it. <Pixel> reads your CSS and converts background, border-radius, border, and box-shadow into pixel art.

APIs

<Pixel> — Wrap any element (Recommended)

import { Pixel } from '@react-pixel-ui/react';

// Tailwind
<Pixel size={6}>
  <div className="bg-gradient-to-r from-red-500 to-blue-500 rounded-xl border-2 border-black">
    Works with Tailwind
  </div>
</Pixel>

// Inline styles
<Pixel size={6}>
  <div style={{ background: '#ff6b6b', borderRadius: 12, border: '2px solid #333' }}>
    Works with inline styles
  </div>
</Pixel>
Prop Type Default Description
size number 4 Pixel block size in CSS px. Larger = blockier.
enabled boolean true Toggle pixelation on/off
children ReactElement required Single HTML element, or a component that forwards its ref to one

Supported CSS properties:

  • background / background-color — solid colors and gradients (linear-gradient, radial-gradient, repeating-*). Alpha-preserving.
  • border-radius — converted to staircase corners (supports per-corner [tl, tr, br, bl])
  • border — pixel art border with staircase corners. Box size is preserved via border-color: transparent (no layout shift, even with box-sizing: content-box).
  • box-shadow — converted to hard drop-shadow (no blur)
  • Reactive updates: the child's className / style props and theme classes on <html> / <body> (Tailwind dark mode, etc.) are automatically observed — no manual re-render needed.

usePixelRef — Ref-based hook

Attach to any element without wrapping. Best for third-party components or when you can't use a wrapper.

When a pixel shadow is present, the hook composes the generated drop-shadow() on the element's parent so the child's staircase clip does not cut it off. Existing parent filters are preserved, and multiple managed children share the parent safely.

import { usePixelRef } from "@react-pixel-ui/react";

function MyComponent() {
  const pixelRef = usePixelRef({ pixelSize: 6 });

  return (
    <div
      ref={pixelRef}
      style={{
        background: "linear-gradient(135deg, #fd79a8, #e84393)",
        borderRadius: 20,
        border: "3px solid #b8256e",
        padding: 16,
      }}
    >
      Pixelated via ref
    </div>
  );
}
Option Type Default Description
pixelSize number 4 Pixel block size
enabled boolean true Toggle pixelation
observeHover boolean true Re-compute on :hover
observeFocus boolean true Re-compute on :focus
observeActive boolean true Re-compute on :active

PixelConfigProvider — Global defaults

Set default pixelSize for all <Pixel> and usePixelRef instances in the tree.

import { PixelConfigProvider } from "@react-pixel-ui/react";

function App() {
  return (
    <PixelConfigProvider config={{ pixelSize: 6 }}>
      {/* All <Pixel> components default to size 6 */}
      <MyPage />
    </PixelConfigProvider>
  );
}
Config Key Type Default Description
pixelSize number 4 Default pixel block size
borderColor string Default for PixelBox; CSS-reading APIs use the computed border color

PixelBox — Explicit props

Use when you want direct control instead of auto-reading CSS.

import { PixelBox } from "@react-pixel-ui/react";

<PixelBox
  width={280}
  height={120}
  pixelSize={6}
  borderRadius={16}
  borderWidth={3}
  borderColor="#333"
  background="linear-gradient(45deg, #ff6b6b, #4ecdc4)"
  shadow={{ x: 4, y: 4, color: "rgba(0,0,0,0.3)" }}
>
  Content
</PixelBox>;
Prop Type Default Description
width number 200 Element width in px
height number 100 Element height in px
pixelSize number 4 Pixel block size
borderRadius number | [number, number, number, number] Corner radius. Array = [topLeft, topRight, bottomRight, bottomLeft]
borderWidth number Border thickness (auto-snapped to pixelSize grid)
borderColor string Any CSS color
background string CSS color or gradient string
shadow { x: number, y: number, color: string } Hard pixel shadow
responsive boolean false Follow the size your CSS gives the box (detected via ResizeObserver) instead of width/height props. Size it with style/className (e.g. style={{ width: '100%', height: 120 }}).

className, style, and other HTML props always land on the root element — the wrapper <div> when a border is used.

PixelButton — Pre-styled button

import { PixelButton } from "@react-pixel-ui/react";

<PixelButton variant="primary" width={160} height={48}>
  Click me
</PixelButton>;
Prop Type Default Description
variant 'primary' | 'secondary' | 'danger' 'primary' Color theme
width number 160 Button width
height number 48 Button height
borderRadius number 8 Corner radius
pixelSize number from context Pixel block size
shadow { x, y, color } auto Pixel shadow

The rendered <button> defaults to type="button" (it won't submit a surrounding form); pass type="submit" explicitly when you want that.

When to use what

Use case API Why
Existing styled elements <Pixel> Reads CSS automatically, zero config
Third-party components usePixelRef Attach via ref, no wrapper div
Full manual control PixelBox Explicit props, no CSS reading
Pre-built buttons PixelButton Ready-to-use with variants

How It Works

Feature CSS Technique
Staircase corners clip-path: polygon() — Bresenham circle algorithm generates stepped polygon
Pixel gradients Composite PNG data URL + image-rendering: pixelated — 2D grid sampling per block with full RGBA alpha
Pixel borders Border color + gradient baked into single PNG with staircase shapes
Hard shadows filter: drop-shadow(blur=0) — follows clip-path contour
Auto-detection getComputedStyle() reads any CSS → converted to pixel art config

Recipes

Dynamic pixel size

function PixelSlider() {
  const [size, setSize] = useState(6);

  return (
    <>
      <input
        type="range"
        min={2}
        max={16}
        value={size}
        onChange={(e) => setSize(+e.target.value)}
      />
      <Pixel size={size}>
        <div
          style={{
            background: "#ff6b6b",
            borderRadius: 12,
            border: "2px solid #333",
          }}
        >
          Size: {size}px
        </div>
      </Pixel>
    </>
  );
}

Per-corner radius

<Pixel size={6}>
  <div
    style={{
      background: "#ffeaa7",
      borderRadius: "24px 4px 24px 4px", // TL TR BR BL
      border: "3px solid #e17055",
      width: 200,
      height: 80,
    }}
  >
    Asymmetric corners
  </div>
</Pixel>

Modern color spaces (oklch / hsl)

// Gradient stops can use any supported color form.
<Pixel size={6}>
  <div
    style={{
      background:
        "linear-gradient(135deg, oklch(0.75 0.2 30), oklch(0.6 0.25 280))",
      borderRadius: 16,
      border: "3px solid hsl(220 40% 20%)",
      padding: 20,
    }}
  >
    oklch + hsl
  </div>
</Pixel>

Translucent gradients

// Alpha is preserved end-to-end via the RGBA composite PNG.
<Pixel size={6}>
  <div
    style={{
      background:
        "linear-gradient(to right, rgba(255,107,107,0.2), rgba(78,205,196,1))",
      borderRadius: 12,
    }}
  >
    Fades from translucent to opaque
  </div>
</Pixel>

Tailwind dark mode

// <Pixel> watches <html> / <body> class changes automatically.
// Toggle a `.dark` class on <html> and the pixel art re-renders
// with the new computed colors.
<Pixel size={6}>
  <div className="bg-white dark:bg-gray-900 border-2 border-black dark:border-white rounded-xl px-4 py-3">
    Auto-adapts to theme
  </div>
</Pixel>

Next.js (App Router)

// app/page.tsx — server component importing <Pixel> works out of the box
import { Pixel } from "@react-pixel-ui/react";

export default function Page() {
  return (
    <Pixel size={6}>
      <div
        style={{
          background: "#6c5ce7",
          borderRadius: 12,
          padding: 20,
          color: "#fff",
        }}
      >
        SSR compatible
      </div>
    </Pixel>
  );
}

The published bundle starts with "use client", so Next.js treats @react-pixel-ui/react as a client module automatically — you don't need to add the directive yourself. <Pixel> renders its child on the server and upgrades to pixel art after hydration.

FAQ

Q: Why does my gradient look smooth instead of pixelated? A: Check that pixelSize is large enough to see distinct blocks. At size={2}, blocks are 2x2 CSS pixels — very small on high-DPI screens. Try size={6} or higher.

Q: Why is the border missing at diagonal corners? A: Make sure you're using <Pixel> or usePixelRef (v2.0.1+). These use composite PNG rendering where border + gradient are baked together with correct staircase shapes.

Q: Does it work with Tailwind CSS? A: Yes. <Pixel> reads getComputedStyle which resolves Tailwind classes into final CSS values. Tailwind dark mode toggling a class on <html> is detected automatically and the pixel art re-renders.

Q: What CSS properties are supported? A: background-color, background-image (linear/radial/repeating gradients), border-radius, border, box-shadow. Other properties (color, font, padding, etc.) are preserved as-is.

Q: Is it SSR compatible? A: Yes. The core package uses pure math (no Canvas, no DOM APIs). Elements render normally on the server and get pixelated on hydration.

Supported CSS values

  • Colors: all 148 CSS named colors, #rgb[a] / #rrggbb[aa], rgb[a]() (comma or modern slash syntax), hsl[a]() (comma or slash), oklch() / oklab(), and color(srgb | srgb-linear | display-p3 ...) are parsed natively. color-mix() and var(--token) are resolved by the browser via getComputedStyle on the <Pixel> / usePixelRef path (Chromium serializes color-mix() results as color(srgb ...), which is supported).
  • Gradients: linear-gradient, radial-gradient, and their repeating-* variants — including Tailwind v4's interpolation hints (to right in oklab), turn/rad/grad angle units, double-position stops, and aspect-ratio-correct corner keywords (to top right). Stops may use any supported color form including oklch().
  • box-shadow: the first non-inset shadow is converted into a hard pixel drop-shadow. Additional shadows and inset shadows are ignored by design (pixel art uses a single hard shadow).
  • Alpha: translucent colors and gradient stops are preserved end-to-end via the composite PNG RGBA encoder (compressed with a built-in dependency-free deflate — data URLs stay in the low-KB range).
  • Graceful degradation: backgrounds the engine can't pixelate (url() images, conic-gradient(), unresolved var()) are left completely untouched — the element keeps its original styling and only the staircase clip-path is applied.

Known limitations

  • <PixelBox> explicit background prop: unlike <Pixel> which reads computed styles, <PixelBox> takes the raw string you pass. It understands hex, named, rgb(), hsl(), oklch(), and color() but not color-mix() or var(--token) (there's no DOM resolution step).
  • Dynamic children via ancestor selectors: <Pixel> observes the child's React props (className, style) and the <html> / <body> theme classes. If an unrelated middle ancestor toggles a class that changes the child via descendant selectors, trigger a parent re-render or use usePixelRef, which listens to style mutations on the managed element directly in addition to hover / focus / active / resize.
  • usePixelRef shadows and parent filters: hard shadows are composed on the managed element's parent to avoid clipping. The hook preserves existing filters and coordinates sibling instances, but CSS selectors that expect the parent's filter to be exactly none may still need an isolated wrapper.

Browser Compatibility

Feature Chrome Firefox Safari Edge
clip-path: polygon() 55+ 54+ 10+ 79+
image-rendering: pixelated 41+ 56+ (crisp-edges) 10+ 79+
filter: drop-shadow() 18+ 35+ 6+ 79+

TypeScript

Fully typed. All components, hooks, and config objects have TypeScript definitions.

import type {
  PixelArtConfig,
  PixelArtStyles,
  PixelShadowConfig,
  BorderRadii,
} from "@react-pixel-ui/react";

Project Structure

packages/
  core/     # Framework-agnostic style generators (zero browser dependency, SSR safe)
  react/    # React hooks & components
apps/
  demo/     # Interactive demo + documentation site

Development

pnpm setup                              # Install + build
pnpm dev --filter=@react-pixel-ui/demo  # Run demo at localhost:3000
pnpm build && pnpm type-check           # Build & verify

Contributing

PRs welcome. Please open an issue first to discuss larger changes.

License

MIT © Todari


React Pixel UI (한국어)

CSS 스타일을 자동으로 픽셀아트로 변환하는 React 라이브러리.

npm install @react-pixel-ui/react
import { Pixel } from "@react-pixel-ui/react";

// 어떤 스타일이든 <Pixel>로 감싸면 픽셀 아트로 변환
<Pixel size={6}>
  <div
    style={{
      background: "linear-gradient(135deg, #ff6b6b, #4ecdc4)",
      borderRadius: 16,
      border: "3px solid #333",
    }}
  >
    자동으로 픽셀화!
  </div>
</Pixel>;

Tailwind, 인라인 스타일, CSS 모듈 모두 지원. Canvas 없음, SSR 호환.

자세한 API 문서는 영어 섹션을 참고하세요.

About

Turn existing CSS into pixel-art React UI with real DOM, no Canvas, and SSR support

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages