Skip to content

polarismorph-code/morphui_react

Repository files navigation

🦎 Morph UI

Automatic UI adaptation for React — Respects your users' preferences without a single line of theme code.

npm version license React PRs Welcome


✨ One component. Zero config. Perfect adaptation.

<MorphProvider>
  <App />
</MorphProvider>

That's it. Your app now adapts to:

  • 🌙 Dark / light mode based on system preference and time of day
  • 👁️ High contrast mode
  • 🎨 Color blindness friendly palette
  • ⚡ Reduced motion
  • 🌍 User language detection

Opt-out — you always stay in control

Before anything else, know this: Morph never forces itself on your UI. Two mechanisms let you draw a hard boundary around anything you don't want touched — styles, zone ordering, font-scale, behavioral tracking. Everything.

1. data-morph-skip — protect a subtree

Put this attribute on any element and Morph stops at that boundary. The element and every descendant keep their original styles, order, and are not tracked by the behavioral observers.

<div data-morph-skip>
  {/* Your brand logo, a third-party widget, a signed-off design system…
      Morph will not modify its colors, reorder it, or scale its text. */}
  <BrandLogo />
</div>

When to use it:

  • Brand assets that must render pixel-perfect (logos, signed designs)
  • Third-party embeds you don't own (payment widgets, chart libraries)
  • <canvas>, <video>, <iframe> content you render yourself
  • Any subtree where you've already hand-tuned the dark/light variants

2. safeMode — run Morph in detect-only mode

Pass safeMode on the provider and Morph will still detect theme, contrast, language, reduced-motion, and system preference — but it will never inject CSS, never modify styles, and never reorder DOM. Perfect for testing, debugging, or gradually rolling out adaptation in production.

<MorphProvider safeMode>
  <App />
</MorphProvider>

Inside your app, useMorph() still returns all the detected values plus safeMode: true, so you can wire your own theming with full signal but zero side-effects:

const { theme, language, safeMode } = useMorph()
// theme is 'dark' or 'light' — apply it yourself, or not.

Bonus — data-morph-force

Need an island of Morph inside a skipped subtree? Add data-morph-force and adaptation resumes from that element down.

<div data-morph-skip>
  <LegacyWidget />
  <section data-morph-force>
    {/* Morph applies again in here, even though the parent is skipped */}
    <ModernCard />
  </section>
</div>

The resolution rule is "closest attribute wins": walking up from any element, the first data-morph-force or data-morph-skip encountered decides.


The problem

Every time you build an app, your designer creates multiple mockups — light theme, dark theme, high contrast variants. Your developer hardcodes every condition. And your user still gets a generic interface that doesn't match their real context.

That's a lot of work for a bad result.


The solution

Morph detects your app's existing theme, reads your styles, and automatically generates the right adaptation — without breaking what you already built.

Same app. Different users. Right interface every time.


Install

npm install @morphuiapp/morphui

Quick start

import { MorphProvider } from '@morphuiapp/morphui'

export default function App() {
  return (
    <MorphProvider>
      <YourApp />
    </MorphProvider>
  )
}

Zero config. Morph detects everything from the browser and applies adaptation locally — no network call, no telemetry:

<MorphProvider>
  <App />
</MorphProvider>

How adaptation works

Morph follows this logic automatically:

App theme Time of day System preference Action
Light Day None ✅ Nothing — stay light
Light Night None 🌙 Switch to dark
Dark Day None ☀️ Switch to light
Dark Night None ✅ Nothing — stay dark
Any Any Dark (manual) 🌙 Always dark
Any Any Light (manual) ☀️ Always light

System preference always wins. Time-based logic only applies when the user hasn't manually set a preference on their device.


Setup by Framework

🎨 With Tailwind CSS

1. Configure Tailwind dark mode

// tailwind.config.js
module.exports = {
  darkMode: 'class', // Required
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
}

2. Use Morph variables as fallbacks (optional)

/* globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --background: var(--morph-bg, #ffffff);
  --foreground: var(--morph-text-primary, #171717);
  --card: var(--morph-card-bg, #ffffff);
  --border: var(--morph-border, #e5e5e5);
}

body {
  background: var(--background);
  color: var(--foreground);
}

3. Wrap your app

import { MorphProvider } from '@morphuiapp/morphui'

export default function App() {
  return (
    <MorphProvider>
      <YourApp />
    </MorphProvider>
  )
}

Morph automatically toggles the .dark class on <html> — all your dark: Tailwind classes work instantly.


🎨 With CSS Variables

/* styles.css */
:root {
  --background: #ffffff;
  --foreground: #171717;
  --primary: #2563eb;
  --card: #f5f5f5;
  --border: #e5e5e5;
}

body {
  background: var(--morph-bg, var(--background));
  color: var(--morph-text-primary, var(--foreground));
}

.card {
  background: var(--morph-card-bg, var(--card));
  border: 1px solid var(--morph-border, var(--border));
}

button {
  background: var(--morph-primary, var(--primary));
  color: var(--morph-primary-text, #ffffff);
}

Morph reads your existing variables and generates adapted versions automatically.


🎨 With plain CSS / SCSS

Morph reads the computed styles of your DOM elements and injects style overrides. For best results, define your colors as CSS variables on :root.

// styles.scss
:root {
  --color-bg: #ffffff;
  --color-text: #171717;
  --color-primary: #2563eb;
}

body {
  background: var(--morph-bg, var(--color-bg));
  color: var(--morph-text-primary, var(--color-text));
}

⚠️ Note: Apps using only hardcoded CSS colors (no CSS variables) will get partial adaptation. For full accuracy, migrate your key colors to CSS variables.


Read the state anywhere

import { useMorph } from '@morphuiapp/morphui'

function MyComponent() {
  const {
    theme,
    highContrast,
    colorBlindMode,
    language,
    prefersReducedMotion,
    adaptation,    // 'none' | 'darken' | 'lighten'
    appBrightness, // original theme of your app
  } = useMorph()

  return (
    <div>
      <p>Theme: {theme}</p>
      <p>Adaptation: {adaptation}</p>
      <p>Language: {language}</p>
    </div>
  )
}

Hooks

useTheme()

import { useTheme } from '@morphuiapp/morphui'

const { theme, prefersReducedMotion } = useTheme()
// theme → 'light' | 'dark'
// prefersReducedMotion → true | false

useAccessibility()

import { useAccessibility } from '@morphuiapp/morphui'

const { highContrast, forcedColors, colorBlindMode } = useAccessibility()
// highContrast → 'normal' | 'high'
// forcedColors → true | false
// colorBlindMode → null | 'deuteranopia' | 'protanopia' | 'tritanopia'

CSS Variables Reference

Variable Description
--morph-bg Main background
--morph-card-bg Card / surface background
--morph-text-primary Primary text
--morph-text-secondary Secondary / muted text
--morph-primary Brand / accent color
--morph-primary-text Text on primary color
--morph-border Border color
--morph-surface Alternative surface
--morph-font-scale Font boost in high contrast (0px default, 2px HC)
--morph-motion Motion multiplier (1 default, 0 reduced motion)

Best Practices for Developers

Follow these guidelines to get the best theme adaptation from Morph.

Use CSS variables for your key colors

/* Best — Morph reads and adapts these automatically */
:root {
  --background: #ffffff;
  --foreground: #171717;
  --primary: #6366f1;
  --card: #f5f5f5;
}
body { background: var(--background); color: var(--foreground); }

Use Tailwind semantic classes instead of arbitrary values

{/* Good — Morph adapts standard Tailwind classes */}
<div className="bg-white text-gray-900 border-gray-200">

{/* Works but harder to adapt — arbitrary hex values */}
<div className="bg-[#f5f5f5] text-[#1c1c1e] border-[#dadce0]">

Give explicit backgrounds to containers

{/* Good — Morph knows this div has a white background */}
<div className="bg-white rounded-lg p-6">
  <p className="text-gray-700">Content</p>
</div>

{/* Bad — div inherits background, Morph can't distinguish it */}
<div className="rounded-lg p-6">
  <p className="text-gray-700">Content</p>
</div>

Avoid !important on colors

/* Bad — blocks Morph */
.my-card { background-color: #fff !important; }

/* Good — Morph can override it */
.my-card { background-color: #fff; }

Prefer CSS classes over inline styles for colors

{/* Bad — inline styles are harder to override */}
<div style={{ background: '#ffffff', color: '#000000' }}>

{/* Good — classes are easy to override */}
<div className="bg-white text-gray-900">

Use data-morph-skip for content areas

{/* PDF viewers, canvas, code editors — don't adapt these */}
<div data-morph-skip>
  <PDFViewer document={doc} />
</div>

{/* Canvas and video are automatically excluded */}
<canvas /> {/* Never touched by Morph */}

Keep gradients on the element, not a parent wrapper

{/* Good — Morph detects and adapts the gradient */}
<section className="bg-linear-to-br from-indigo-50 via-white to-blue-50">

{/* Bad — gradient on a wrapper div without explicit class */}
<div style={{ background: 'linear-gradient(...)' }}>

For Ant Design / MUI / Chakra — let the framework handle structure

Morph automatically detects and adapts Ant Design, MUI, and Chakra UI components. No special config needed. Just wrap your app:

<MorphProvider>
  <ConfigProvider> {/* antd */}
    <App />
  </ConfigProvider>
</MorphProvider>

Troubleshooting

Dark mode not activating on Tailwind

Make sure you have darkMode: 'class' in tailwind.config.js — this is required.


Colors not adapting at all

1. MorphProvider not at root level

// ❌ Wrong
function Page() {
  return <MorphProvider><Content /></MorphProvider>
}

// ✅ Correct — wrap at the very top
function App() {
  return <MorphProvider><Page /></MorphProvider>
}

2. Colors hardcoded with !important

/* ❌ Blocks Morph */
.my-class { color: #000 !important; }

/* ✅ Use CSS variables */
.my-class { color: var(--morph-text-primary); }

3. Inline styles

{/* ❌ Can't be overridden */}
<div style={{ color: '#000' }}>Text</div>

{/* ✅ Use CSS classes */}
<div className="text-foreground">Text</div>

Next.js App Router setup

MorphProvider uses React hooks — it must be in a Client Component:

// app/providers.tsx
'use client';
import { MorphProvider } from '@morphuiapp/morphui';

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <MorphProvider>
      {children}
    </MorphProvider>
  );
}
// app/layout.tsx
import { Providers } from './providers';

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

For Vite / CRA, add this in index.html before React loads:

<script>
  if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
    document.documentElement.classList.add('dark');
  }
</script>

Text unreadable after adaptation

Morph includes an automatic contrast fixer that checks every text element against its real background. If issues persist:

  • Make sure elements have explicit backgrounds (not transparent)
  • Update to the latest version: npm update @morphuiapp/morphui

Framework support

Framework Status
React 18+ ✅ Supported
Next.js 13+ App Router ✅ Supported
Next.js 12 Pages Router ✅ Supported
Vite + React ✅ Supported
Tailwind CSS v3 / v4 ✅ Supported
shadcn/ui ✅ Supported
Ant Design v5+ ✅ Supported
Material UI (MUI) v5+ ✅ Supported
Chakra UI v3 ✅ Supported
CSS / CSS Variables ✅ Supported
SCSS / SASS ✅ Supported (best with CSS variables)
Plain CSS ✅ Supported
Vue.js 🔜 On the roadmap
React Native 🔜 On the roadmap

Browser support

Browser Minimum version
Chrome 76+
Edge 79+
Firefox 67+
Safari 12.1+

Roadmap

✅ MVP (current)

  • Automatic light / dark theme detection
  • Time-based adaptation (day / night)
  • System preference — always takes priority
  • High contrast mode
  • Color blindness mode
  • Reduced motion support
  • Language detection
  • Tailwind CSS v3 / v4 support
  • shadcn/ui support
  • Ant Design support
  • Material UI (MUI) support
  • Chakra UI v3 support
  • CSS / CSS Variables / SCSS support
  • Local HSL-based dark generation (no backend, no telemetry)
  • Automatic WCAG contrast fixes
  • Gradient adaptation (from-, via-, to-*)
  • Inline style adaptation
  • Portal / modal / dropdown detection
  • Canvas / PDF / video exclusion

🔜 Roadmap

  • Vue.js support
  • React Native support

Want behavioral intelligence? Zone tracking, navigation-pattern reorder, heatmaps, AI palette generation, content morphing and the SaaS dashboard live in the proprietary V2 build (separate package). The OSS V1 here is a fully self-contained system-detection + theme-adaptation layer with zero network calls.


Contributing

git clone https://github.com/morphuiapp/morphui
cd morph-ui
npm install
npm run dev

Open an issue before submitting a pull request for major changes.


License

MIT © Cabraule


Built with ❤️ — Because every user deserves an interface made for them.

About

Intelligent UI SDK for React. Automatic dark mode, accessibility and behavioral adaptation. One line of code. Zero config.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors