ecosystem-integrations

v2026.09.25

MUI ecosystem — FormEngine, react-jsonschema-form, Uniforms, schema-driven forms, Tailwind interop, Framer Motion, and third-party component libraries

GitHub
Install command
npx skhub add thelobbi/ecosystem-integrations
Markdown
SKILL.md

MUI Ecosystem Integrations

Third-party libraries, schema-driven form builders, styling interop, and animation integrations that extend MUI's capabilities.


Schema-Driven Form Builders

FormEngine with MUI

JSON config renders live MUI forms with validation and event wiring.

npm install @react-form-builder/core @react-form-builder/components-material-ui
import { FormViewer } from '@react-form-builder/core';
import { view as muiView } from '@react-form-builder/components-material-ui';

const contactForm = {
  tooltipType: 'MuiTooltip',
  errorType: 'MuiErrorWrapper',
  form: {
    key: 'Screen',
    type: 'Screen',
    children: [
      {
        key: 'name',
        type: 'MuiTextField',
        props: { label: { value: 'Full Name' } },
        schema: { validations: [{ key: 'required' }] },
      },
      {
        key: 'email',
        type: 'MuiTextField',
        props: { label: { value: 'Email' } },
        schema: { validations: [{ key: 'required' }, { key: 'email' }] },
      },
      {
        key: 'role',
        type: 'MuiSelect',
        props: {
          label: { value: 'Role' },
          options: { value: [
            { value: 'admin', label: 'Admin' },
            { value: 'user', label: 'User' },
          ]},
        },
      },
      {
        key: 'submit',
        type: 'MuiButton',
        props: {
          children: { value: 'Submit' },
          variant: { value: 'contained' },
        },
        events: {
          onClick: [
            { name: 'validate', type: 'common', args: { failOnError: true } },
            { name: 'onSubmit', type: 'custom' },
          ],
        },
      },
    ],
  },
};

function DynamicForm() {
  return (
    <FormViewer
      view={muiView}
      getForm={() => JSON.stringify(contactForm)}
      actions={{
        onSubmit: (e) => console.log('Form data:', e.data),
      }}
    />
  );
}

MUI Components Pack maps JSON types to real MUI components: MuiTextField, MuiSelect, MuiCheckbox, MuiSwitch, MuiButton, MuiDialog, MuiCard, MuiAutocomplete, MuiDatePicker, etc.

Conditional Rendering with renderWhen:

{
  "key": "discountCode",
  "type": "MuiTextField",
  "props": { "label": { "value": "Discount Code" } },
  "renderWhen": { "value": "form.data.hasDiscount === true" }
}

react-jsonschema-form with MUI

JSON Schema → MUI form rendering.

npm install @rjsf/core @rjsf/mui @rjsf/validator-ajv8
import Form from '@rjsf/mui';
import validator from '@rjsf/validator-ajv8';

const schema = {
  type: 'object',
  required: ['name', 'email'],
  properties: {
    name: { type: 'string', title: 'Full Name' },
    email: { type: 'string', title: 'Email', format: 'email' },
    age: { type: 'integer', title: 'Age', minimum: 18 },
    role: {
      type: 'string',
      title: 'Role',
      enum: ['admin', 'editor', 'viewer'],
      enumNames: ['Administrator', 'Editor', 'Viewer'],
    },
    bio: { type: 'string', title: 'Biography' },
  },
};

const uiSchema = {
  bio: { 'ui:widget': 'textarea', 'ui:options': { rows: 4 } },
  role: { 'ui:widget': 'select' },
  'ui:order': ['name', 'email', 'age', 'role', 'bio'],
};

function JsonSchemaForm() {
  return (
    <Form
      schema={schema}
      uiSchema={uiSchema}
      validator={validator}
      onSubmit={({ formData }) => console.log(formData)}
    />
  );
}

Custom Widgets and Templates:

const widgets = {
  DateWidget: (props) => (
    <DatePicker value={dayjs(props.value)} onChange={(d) => props.onChange(d?.toISOString())} />
  ),
};

const templates = {
  ObjectFieldTemplate: (props) => (
    <Grid container spacing={2}>
      {props.properties.map((prop) => (
        <Grid key={prop.name} size={{ xs: 12, md: 6 }}>
          {prop.content}
        </Grid>
      ))}
    </Grid>
  ),
};

<Form schema={schema} widgets={widgets} templates={templates} validator={validator} />

Uniforms with MUI Bridge

Multi-schema form engine with pluggable styling bridges.

npm install uniforms uniforms-bridge-json-schema uniforms-mui
import { AutoForm, AutoField, SubmitField } from 'uniforms-mui';
import { JSONSchemaBridge } from 'uniforms-bridge-json-schema';
import Ajv from 'ajv';

const ajv = new Ajv({ allErrors: true });
const schema = {
  type: 'object',
  properties: {
    name: { type: 'string' },
    email: { type: 'string', format: 'email' },
  },
  required: ['name', 'email'],
};

const bridge = new JSONSchemaBridge({ schema, validator: ajv.compile(schema) });

<AutoForm schema={bridge} onSubmit={(data) => save(data)}>
  <AutoField name="name" />
  <AutoField name="email" />
  <SubmitField />
</AutoForm>

When to Use What

ToolBest For
FormEngine + MUIAdmin builders, dynamic forms from metadata, runtime JSON config
react-jsonschema-form + MUIJSON Schema-driven forms, API-defined schemas
Uniforms + MUIMulti-schema support, rapid prototyping
React Hook Form + MUIHand-coded forms with type-safe validation (Zod)
Formik + MUILegacy projects already using Formik

Tailwind CSS + MUI Interop

Using MUI with Tailwind

MUI and Tailwind can coexist. Key: Tailwind's preflight conflicts with MUI's CssBaseline.

Setup (tailwind.config.ts):

export default {
  // Important: disable preflight to avoid conflicts with CssBaseline
  corePlugins: {
    preflight: false,
  },
  // Scope Tailwind to avoid class conflicts
  important: '#root', // or use selector strategy
  content: ['./src/**/*.{ts,tsx}'],
};

Emotion + Tailwind ordering — ensure Emotion styles take precedence:

import createCache from '@emotion/cache';
import { CacheProvider } from '@emotion/react';

const cache = createCache({
  key: 'css',
  prepend: true, // Emotion styles inserted BEFORE Tailwind → Tailwind can override
});

<CacheProvider value={cache}>
  <ThemeProvider theme={theme}>
    <CssBaseline />
    <App />
  </ThemeProvider>
</CacheProvider>

Using Tailwind classes on MUI components:

// Tailwind classes work alongside sx prop
<Button className="rounded-full shadow-lg" sx={{ px: 4 }}>
  Mixed styling
</Button>

// Use Tailwind for layout, MUI for component styles
<Box className="flex items-center gap-4 p-6">
  <TextField label="Name" fullWidth />
  <Button variant="contained">Save</Button>
</Box>

Base UI + Tailwind (Headless + Utility)

The cleanest integration: Base UI hooks for logic/a11y, Tailwind for all visuals.

import { useButton } from '@mui/base/useButton';
import clsx from 'clsx';

function TailwindButton({ children, variant = 'primary', ...props }) {
  const { getRootProps, active, disabled, focusVisible } = useButton(props);

  return (
    <button
      {...getRootProps()}
      className={clsx(
        'rounded-lg px-4 py-2 font-medium transition-all duration-150',
        variant === 'primary' && 'bg-blue-600 text-white hover:bg-blue-700 active:bg-blue-800',
        variant === 'secondary' && 'bg-slate-200 text-slate-900 hover:bg-slate-300',
        disabled && 'opacity-50 cursor-not-allowed',
        focusVisible && 'ring-2 ring-blue-400 ring-offset-2',
        active && 'scale-95',
      )}
    >
      {children}
    </button>
  );
}

Framer Motion + MUI

AnimatePresence with MUI Dialog

import { AnimatePresence, motion } from 'framer-motion';
import Dialog from '@mui/material/Dialog';

function AnimatedDialog({ open, onClose, children }) {
  return (
    <AnimatePresence>
      {open && (
        <Dialog
          open={open}
          onClose={onClose}
          PaperComponent={motion.div}
          PaperProps={{
            initial: { opacity: 0, scale: 0.9, y: 20 },
            animate: { opacity: 1, scale: 1, y: 0 },
            exit: { opacity: 0, scale: 0.95, y: -10 },
            transition: { type: 'spring', stiffness: 300, damping: 25 },
          }}
        >
          {children}
        </Dialog>
      )}
    </AnimatePresence>
  );
}

Base UI + Tailwind + Framer Motion (Hardware Switch)

import { useSwitch } from '@mui/base/useSwitch';
import { motion } from 'framer-motion';

function HardwareSwitch({ label, ...props }) {
  const { checked, disabled, focusVisible, getInputProps } = useSwitch(props);

  return (
    <label className="inline-flex items-center gap-3 cursor-pointer select-none">
      {label && <span className="text-sm font-medium text-slate-200">{label}</span>}
      <div className="relative">
        <input {...getInputProps()} className="sr-only" />

        {/* Track */}
        <motion.div
          className={`
            relative flex h-9 w-16 items-center rounded-full px-1
            border border-slate-700/80 bg-slate-900/90
            shadow-[0_0_0_1px_rgba(15,23,42,0.9),0_8px_18px_rgba(0,0,0,0.85)]
            ${disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'}
          `}
          animate={{
            boxShadow: checked
              ? '0 0 0 1px rgba(56,189,248,0.75), 0 16px 35px rgba(8,47,73,0.9)'
              : '0 0 0 1px rgba(15,23,42,0.95), 0 10px 25px rgba(0,0,0,0.9)',
          }}
          whileTap={{ scale: 0.97 }}
          transition={{ type: 'spring', stiffness: 500, damping: 32 }}
        >
          {/* LED indicator */}
          <motion.div
            className="pointer-events-none absolute left-1 top-1/2 h-1.5 w-1.5 -translate-y-1/2 rounded-full"
            animate={{
              backgroundColor: checked ? 'rgba(56,189,248,1)' : 'rgba(148,163,184,0.5)',
              boxShadow: checked ? '0 0 10px rgba(56,189,248,0.9)' : '0 0 4px rgba(148,163,184,0.6)',
            }}
          />

          {/* Thumb */}
          <motion.div
            className="relative z-10 h-7 w-7 rounded-full bg-gradient-to-br from-slate-100 to-slate-400 shadow-lg flex items-center justify-center"
            layout
            animate={{ x: checked ? 26 : 0 }}
            transition={{ type: 'spring', stiffness: 550, damping: 30, mass: 0.4 }}
          >
            <motion.div
              className="h-4 w-4 rounded-full bg-slate-900/90 shadow-[inset_0_0_4px_rgba(0,0,0,0.75)]"
              animate={{
                boxShadow: checked
                  ? 'inset 0 0 6px rgba(56,189,248,0.9)'
                  : 'inset 0 0 3px rgba(0,0,0,0.8)',
              }}
            />
          </motion.div>

          {/* Focus ring */}
          {focusVisible && (
            <span className="pointer-events-none absolute -inset-1 rounded-full ring-2 ring-sky-400/80 ring-offset-2 ring-offset-slate-900" />
          )}
        </motion.div>
      </div>
    </label>
  );
}

Animated Lists with MUI

import { AnimatePresence, motion } from 'framer-motion';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';

function AnimatedList({ items }) {
  return (
    <List>
      <AnimatePresence initial={false}>
        {items.map((item) => (
          <motion.div
            key={item.id}
            initial={{ opacity: 0, height: 0 }}
            animate={{ opacity: 1, height: 'auto' }}
            exit={{ opacity: 0, height: 0 }}
            transition={{ type: 'spring', stiffness: 300, damping: 25 }}
          >
            <ListItem>
              <ListItemText primary={item.name} secondary={item.email} />
            </ListItem>
          </motion.div>
        ))}
      </AnimatePresence>
    </List>
  );
}

Custom Theme-Registered Components

Register your own component in the MUI theme so it responds to theme changes like a built-in.

// 1. Define the component
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';

interface StatProps {
  label: string;
  value: string | number;
  trend?: 'up' | 'down' | 'flat';
}

const StatRoot = styled(Box, {
  name: 'MuiStat',    // Register in theme as MuiStat
  slot: 'Root',
})(({ theme }) => ({
  display: 'flex',
  flexDirection: 'column',
  gap: theme.spacing(0.5),
  padding: theme.spacing(2, 3),
  borderRadius: theme.shape.borderRadius * 2,
  border: `1px solid ${theme.palette.divider}`,
}));

export function Stat({ label, value, trend }: StatProps) {
  return (
    <StatRoot>
      <Typography variant="caption" color="text.secondary">{label}</Typography>
      <Typography variant="h4">{value}</Typography>
      {trend && <TrendIndicator trend={trend} />}
    </StatRoot>
  );
}

// 2. Register in theme
const theme = createTheme({
  components: {
    MuiStat: {
      defaultProps: {},
      styleOverrides: {
        root: ({ theme }) => ({
          backgroundColor: theme.palette.background.paper,
          '&:hover': { borderColor: theme.palette.primary.main },
        }),
      },
      variants: [
        {
          props: { variant: 'highlighted' },
          style: ({ theme }) => ({
            borderColor: theme.palette.primary.main,
            backgroundColor: theme.palette.primary.light + '10',
          }),
        },
      ],
    },
  },
});

// 3. TypeScript augmentation
declare module '@mui/material/styles' {
  interface Components {
    MuiStat?: {
      defaultProps?: Partial<StatProps>;
      styleOverrides?: { root?: any };
      variants?: any[];
    };
  }
}

Theme as Rules Engine

Encode design decisions in the theme and enforce at compile time:

// Restrict allowed Button variants in your design system
declare module '@mui/material/Button' {
  interface ButtonPropsVariantOverrides {
    contained: true;
    outlined: true;
    text: false;     // ← disable 'text' variant at compile time
    dashed: true;    // ← add custom variant
    gradient: true;  // ← add custom variant
  }

  interface ButtonPropsColorOverrides {
    inherit: false;  // ← disable inherit color
    brand: true;     // ← add custom brand color
    neutral: true;   // ← add custom neutral color
  }

  interface ButtonPropsSizeOverrides {
    extraLarge: true; // ← add custom size
  }
}

Now <Button variant="text"> is a TypeScript error, enforcing design rules at compile time.

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.25

Published

Sep 25, 2026

Category

Uncategorized

License

MIT

Source path

plugins/mui-expert/skills/ecosystem-integrations

Default branch

main

Latest commit

2f1269c

Tree SHA

629e050