Connect your library

Integrating Your Own Components

This guide shows how to bring your own React components into UXPin Merge. By the end, your components appear in the UXPin Editor and stay in sync with your code. It’s written for developers who work with a React component library in Git.

Setting up the environment and rendering your first component shouldn’t take more than 30 minutes. A complete integration of a complex design system can take from 2 hours to 3–4 days.

Two ways to integrate

Clean integration. Merge works directly with your production code. No wrappers or code changes are necessary.

  • Benefits: a simple workflow connecting designers and engineers, effortless maintenance (no extra work to update components in UXPin) and fast integration.
  • Trade-offs: you need to follow Merge’s technical and code requirements, and you can’t modify component code for the benefit of designers using UXPin.

Wrapped integration. Merge works with wrappers for your components. Wrappers are typically HOC components.

  • Benefits: a flexible integration with fewer problems meeting Merge’s requirements, and the ability to modify components to meet designers’ needs (for example, controlled checkboxes).
  • Trade-offs: code maintenance is more complex and may need manual updates, and integration takes longer because you create a wrapper for every component.

Choose clean integration if your components already meet Merge’s requirements. Choose wrapped integration if you need to change how components behave for designers, or can’t change your source code.

Requirements

  • Components: React components. React 16 to 18 are supported, and React 19 support is experimental. Web Components can be used through React wrappers.
  • Node.js: 16 or later, to run the Merge CLI.
  • Bundler: webpack 4 or 5 and webpack-cli 4 or 5, with a config that joins styles with components.
  • Git: your library is in a Git repository.
  • Directory structure: a single component per directory, and the filename defines the component name.
  • Exports: component files have to contain export default providing the component.
  • Dialects: JavaScript (properties defined with PropTypes), Flow (properties defined with types) and TypeScript (properties defined with interfaces).
  • Access to Merge: if you don’t have it yet, contact support at hello@uxpin.com.
  • Browser: UXPin works best in the Chrome browser.

Clean integration, step by step

1. Install the Merge CLI in your project:

npm install @uxpin/merge-cli --save-dev

2. Create the config files. Run npx uxpin-merge init. It creates uxpin.config.js, uxpin.webpack.config.js, a wrapper in src/components/UXPinWrapper/UXPinWrapper.js and an example Button in src/components/Button. It doesn’t overwrite files that already exist.

3. Add your components to uxpin.config.js. To make debugging easier, start with one component and add the rest one by one. A starting config looks like this:

module.exports = {
  components: {
    categories: [
      {
        name: 'General',
        include: ['src/components/Button/Button.js'],
      },
    ],
    wrapper: 'src/components/UXPinWrapper/UXPinWrapper.js',
    webpackConfig: 'uxpin.webpack.config.js',
  },
  name: 'My Design System',
};

See Config File for all options.

4. Check your webpack config. Merge uses webpack to bundle JavaScript, CSS and the assets you import, such as icons and font files, and it merges your config with its own settings. If your config is complex, create a separate one for Merge. See Webpack Configuration.

5. If you need to provide a theme or other context for your components, create a wrapper component. It’s a HOC you can place in any directory in your repository. Here’s a wrapper with a theme provider:

import React from 'react';
import { ThemeProvider } from 'your-ui-library';
import theme from './theme';

export default function UXPinWrapper({ children }) {
  return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
}

See Wrapper Component for more.

6. Add a preset, so Merge knows how to render the component first. Put preset files in a presets folder inside the component’s directory. A preset file name is a number, a dash and a name, for example 0-default.jsx. The number sets the order.

import React from 'react';
import Button from '../Button';

export default (
  <Button uxpId="button1" label="Button" disabled={false} />
);

The uxpId is a unique identifier. Two elements in one preset can’t have the same uxpId.

7. Preview your components. Run this from the root of your repository:

npx uxpin-merge

This starts experimental mode, where you can see your library and components in the UXPin Editor.

8. Push your library to UXPin. First create a library in the UXPin Editor: select “Import React or Web Components from Git repo” and copy the authentication token. Don’t share it and don’t commit it to Git.

Then run:

npx uxpin-merge push --token <your-library-token>

The wrapper and webpackConfig paths come from uxpin.config.js, so you don’t need to pass them as flags.

To send components automatically on every change, use a CI server. See Connecting code to UXPin (CI & push). A one-time push is an alternative, but only if you don’t plan frequent updates.

Wrapped integration, step by step

1. Decide where the wrappers live. You can create new directories for wrapper components inside your existing repository, for example ./src/components/Button/Merge/Button/Button.js, or create a new repository for them. Both work the same in this guide, but they change how you manage and release your components.

2. Install the Merge CLI as a dev dependency, as in step 1 above.

3. Create your first wrapper. A typical wrapper is a HOC React component that imports and returns your original component:

import React from 'react';
import PropTypes from 'prop-types';
import { Button as ButtonM } from '../../Button';

function Button(props) {
  return <ButtonM {...props}>{props.children}</ButtonM>;
}

Button.propTypes = {
  children: PropTypes.node,
  size: PropTypes.oneOf(['small', 'large', 'medium']),
};

export default Button;

Merge requires the parent directory and the exported component to have the same name. This name shows up in the UXPin Editor and in Spec mode, so the wrapper must be named like the original component. To avoid a name conflict, import the original under a different name, such as ButtonM.

4. Continue with steps 2 to 8 of the clean integration, with one difference: point include in uxpin.config.js at your wrapper files, for example src/components/Button/Merge/Button/Button.js.

Troubleshooting

  1. Some styles appear broken. Your styles may interfere with UXPin CSS, or the other way round. Scope your styles locally to avoid conflicts with UXPin CSS.
  2. My components are distributed as individual npm packages. Consider writing a wrapper (HOC) for every component and importing your system as a dependency. See this example. You can also use the AI-Assisted Merge Integration, which works with npm packages.

Next steps

Config File

The config file tells Merge which components to import into UXPin and describes your library. It can also hold the paths to your webpack config and, optionally, a wrapper component.

Where it lives

The config file is a JavaScript file named uxpin.config.js stored in the root directory of your project. To use another path, pass it with the --config option (see CLI Tool). The file exports a JavaScript object with module.exports.

To create a starting config, run npx uxpin-merge init.

./uxpin.config.js

A basic config

module.exports = {
  components: {
    categories: [
      {
        name: 'General',
        include: ['src/components/Button/Button.js'],
      },
    ],
    wrapper: 'src/components/UXPinWrapper/UXPinWrapper.js',
    webpackConfig: 'uxpin.webpack.config.js',
  },
  name: 'My Design System',
};

The name of the library shows up in the UXPin Editor once you upload your library to UXPin.

UXPin dashboard

Options

  • name: The name of your library, shown in the UXPin Editor. If you leave it out, the default name is “Library from Code”.
  • components.categories: A list of categories. Each category has a name and an include key.
  • categories[].name: The category name, shown in the component list in the UXPin Editor.
  • categories[].include: A glob pattern, or a list of glob patterns, matching the component files in the category.
  • components.wrapper: The path to a wrapper component. Optional. See Wrapper Component.
  • components.webpackConfig: The path to your webpack config. See Webpack Configuration.
  • components.pageHeadTags: HTML tags, as a string or a list of strings, that Merge adds to the <head> of the page. Use it, for example, to load fonts.
  • settings: An object that Merge sends to UXPin with your library. See Library settings below.

Categories

The category names appear in the list of components in the UXPin Editor. If you plan to import many components, create several categories to make navigation in UXPin easier. To add a category, add another object with the keys name and include to the array:

categories: [
  {
    name: 'General',
    include: [
      'src/components/Button/Button.js',
      'src/components/Link/Link.js',
    ],
  },
  {
    name: 'Navigation',
    include: [
      'src/components/Tabs/Tabs.js',
      'src/components/Tab/Tab.js',
    ],
  },
],

If a category should contain only one component, you can assign the path directly to include:

{
  name: 'Category',
  include: 'src/components/YourComponent/YourComponent.js',
}

Glob patterns

include also accepts glob patterns:

module.exports = {
  components: {
    categories: [
      {
        name: 'General',
        include: [
          'src/library/Avatar/Avatar.js',
          'src/library/Checkbox/!(__tests__)/*.js',
          'src/library/Form/!(__tests__)/*.js',
        ],
      },
      {
        name: 'Card',
        include: 'src/library/Card/!(__tests__)/*.js',
      },
      {
        name: 'Icons',
        include: 'src/library/Icon/!(index).js',
      },
    ],
    wrapper: 'src/components/UXPinWrapper/UXPinWrapper.js',
    webpackConfig: 'uxpin.webpack.config.js',
  },
};

Default config

If uxpin.config.js doesn’t exist or isn’t in the root directory, Merge prints a message and loads the default config:

module.exports = {
  components: {
    categories: [
      {
        name: 'Uncategorized',
        include: [
          'src/components/*/*.{js,jsx,ts,tsx}',
          'src/*/*.{js,jsx,ts,tsx}',
        ],
      },
    ],
  },
  name: 'Library from Code',
};

Library settings

The settings object is sent to UXPin with your library. It turns on features for your library in UXPin. Two are documented:

module.exports = {
  components: { /* ... */ },
  name: 'My Design System',
  settings: {
    useAI: {
      name: 'My Design System',
      documentationUrl: '',
      systemPrompt: '',
    },
    useUXPinProps: true,
  },
};

The settings option needs @uxpin/merge-cli 3.4.3 or newer.

Webpack Configuration

Merge uses webpack to bundle your component files into one JavaScript bundle that it sends to UXPin. This page explains how to set up your webpack config, so Merge can bundle your components together with their styles and assets.

Requirements

  • webpack 4 or 5, and webpack-cli 4 or 5, installed in your project.
  • The location of your webpack config stored in the Config File under webpackConfig. You can also pass it with the --webpack-config option. See CLI Tool.

How Merge uses your config

Merge combines your webpack config with its own settings. Its settings define the entry point, the output (a CommonJS library file named designsystemlibrary.js) and the build mode. If you don’t provide a config, Merge uses only its own settings.

Rules for your config

All the code and encoded files are merged into one bundle sent to UXPin. As a result, your webpack config has to be built in a way that doesn’t export any external files.

If you use webpack 5, asset modules do the same job: a rule with type: 'asset/inline' inlines the imported file into the bundle as a data URI.

{
  test: /\.(png|jpe?g|gif|svg|woff2?|ttf|eot)$/,
  type: 'asset/inline',
},

If this approach doesn’t fit your current webpack config, create a separate config for Merge.

A basic config

This is the config that npx uxpin-merge init creates in uxpin.webpack.config.js. It serves a repository with CSS loaded through style-loader and css-loader.

const path = require('path');
const webpack = require('webpack');

module.exports = {
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: 'bundle.js',
    publicPath: '/',
  },
  resolve: {
    modules: [__dirname, 'node_modules'],
    extensions: ['*', '.js', '.jsx'],
  },
  devtool: 'eval-source-map',
  module: {
    rules: [
      {
        test: /\.(s*)css$/,
        use: [
          { loader: 'style-loader' },
          { loader: 'css-loader', options: { importLoaders: 2 } },
        ],
      },
      {
        loader: require.resolve('babel-loader', { paths: ['./node_modules/@uxpin/merge-cli'] }),
        test: /\.js?$/,
        exclude: /node_modules/,
        options: {
          presets: [
            require.resolve('@babel/preset-env', { paths: ['./node_modules/@uxpin/merge-cli'] }),
            require.resolve('@babel/preset-react', { paths: ['./node_modules/@uxpin/merge-cli'] }),
          ],
        },
      },
    ],
  },
};

Babel comes with the Merge CLI, which is why the config resolves babel-loader, @babel/preset-env and @babel/preset-react from it. style-loader and css-loader are not part of the Merge CLI, so install them in your project.

More examples

Add these rules to the module.rules array of the basic config.

CSS-in-JS (Emotion) and SVG icons inlined into an Icon component:

{
  test: /\.svg$/,
  exclude: /node_modules/,
  loader: 'svg-react-loader',
},

Sass and inlined SVG icons:

{
  test: /\.svg$/,
  loader: 'svg-react-loader',
},
{
  test: /\.(s*)css$/,
  use: [
    { loader: 'style-loader' },
    { loader: 'css-loader', options: { importLoaders: 2 } },
    { loader: 'sass-loader' },
  ],
},

Next steps

Wrapper Component

A wrapper component is a React component that wraps everything Merge renders in UXPin. Use it to provide a theme, import a shared style file, or give your components any other context they need. A wrapper is optional. It’s typically a HOC React component.

You can store a wrapper component anywhere in the repository, but you have to refer to its location in the Config File under wrapper. You can also pass it with the --wrapper option. See CLI Tool.

module.exports = {
  components: {
    categories: [
      {
        name: 'First Category',
        include: [
          'src/components/FirstComponent/FirstComponent.js',
          'src/components/SecondComponent/SecondComponent.js',
        ],
      },
    ],
    wrapper: 'src/components/UXPinWrapper/UXPinWrapper.js',
    webpackConfig: 'uxpin.webpack.config.js',
  },
  name: 'Name of your Library',
};

npx uxpin-merge init creates a basic wrapper in src/components/UXPinWrapper/UXPinWrapper.js.

A basic wrapper

The most basic wrapper component is a React component that returns its children:

import React from 'react';

export default function UXPinWrapper({ children }) {
  return children;
}

Passing a theme provider

A wrapper can implement a theme provider. As a result, the theme styles are attached to every component rendered in UXPin:

import React from 'react';
import { ThemeProvider } from 'your-ui-library';

const theme = {
  palette: {
    primary: { main: '#006CFF' },
    secondary: { main: '#444444' },
  },
};

export default function UXPinWrapper({ children }) {
  return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
}

Importing a common style file

A wrapper can also import a common style file. Webpack captures any import in the wrapper and adds it, after compilation, to the JavaScript package sent to UXPin:

import React from 'react';
import '../../styles/sass/common.scss';

export default function UXPinWrapper({ children }) {
  return children;
}

Loading fonts

For fonts stored in external services, such as Google Fonts or Adobe Fonts, add the tags to pageHeadTags in the config file. Merge adds them to the <head> of the page.

module.exports = {
  components: {
    categories: [ /* ... */ ],
    pageHeadTags: [
      '<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">',
      '<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">',
    ],
  },
  name: 'Name of your Library',
};

For font files in your repository, import them directly, so webpack handles them. See Webpack Configuration.

Next steps

Storybook integration

UXPin Merge Storybook integration

Design faster with Storybook components. Connect your own Storybook, public or private, to your UXPin account and use its interactive, code-based components in the UXPin Editor. You build prototypes with the same components your developers use, and you don’t need to know how to code.

Merge supports all Storybook frameworks.

Storybook integration is an alternative to the Git repository integration. Both let you design with pre-built code components. The main difference is how you add the components to UXPin.

Note

This page doesn’t show how to create a Storybook. It shows how to integrate an existing one with UXPin. Storybook is a development environment for UI components: it lets you browse components as a library, add interactions, view them in different states and test them in a safe environment. For details, see the Storybook website and its introduction guide.

Before you start

You need two things:

  1. Access to Merge in UXPin. If you don’t have it yet, contact support at hello@uxpin.com.
  2. Your Storybook URL. If your Storybook is open or public, you can go straight to the integration. If it’s private, you deploy it to UXPin with the Storybook deployer, as described below.
Note

If you use Vite with Storybook, add one more setting before you integrate. Vite assumes assets are in the root directory, so in your Storybook main.* file, add config.base = '' in viteFinal. See the Vite documentation for details.

Integrate a public Storybook

1. Open a new or existing prototype from the UXPin Dashboard.

2. Select the ‘Design System Libraries’ tab at the bottom left, then click ‘+ New Library’.

3. Select ‘Import Components from Storybook’, enter the URL of your Storybook and save the library.

Integrate a private Storybook

1. Follow the same steps as for a public Storybook, but select “Private” and copy the token.

2. Install the Storybook deployer in your design system:

npm install @uxpin/storybook-deployer --save-dev

3. Build your Storybook as static files, then deploy it to UXPin:

npx uxpin-storybook-deployer -t TOKEN -s path/to/your/storybook
  • -s, --source <path>: the directory with your static Storybook build. Default: storybook-static.
  • -t, --token <token>: the auth token for your library.
Note

With a private Storybook, your Storybook is protected by a UXPin login session. This also means you can’t see Storybook components in Preview without logging in to an account that has permission to the library.

You’re good to go. Start dragging and dropping the components.

Change component properties

With Storybook’s mechanism, Args, you can change properties, slots, styles, inputs and more dynamically in the UXPin Editor. There’s no need to alter a component in Storybook: you can do it while designing in UXPin. Learn more about Args.

Just make sure:

  1. You have Storybook 6 or newer.
  2. Your story is written the way Storybook defines it. See Storybook: using args.
Note

There’s a similar feature called “knobs”, which isn’t supported, because Storybook announced it would be deprecated in future versions. See how Controls replace knobs.

Update Storybook components in UXPin

After you integrate a public Storybook with UXPin, components update automatically whenever you change a component in your Storybook. The updated component looks and works exactly like in your Storybook, so don’t worry about components being a bit off after the sync.

For a private Storybook, run the deployer again after you change your components, because the deployer sends a static build.