QuickUIDesign

About QuickUIDesign Template

QuickUIDesign is a React-based UI development template designed specifically for Unreal Engine 5 (UE5). It provides a complete toolchain for creating interactive UE5 interfaces using modern frontend technologies, enabling developers to build high-performance game UI with React.

Version History

  • v1.0.0 Initial release with core template structure, UE5 connection demo, asset toolchain, and Rspack build configuration.
  • v1.1.0 Added media asset packaging support, enabling images, audio, video, and other media assets to be bundled into HTML files.
  • v1.2.0 Refactored to routing architecture (route-template), introducing React Router v7 routing system, framer-motion page transition animations, and ScreenAnchor screen positioning component system.
  • v1.2.5 Added clickdeck-core module, providing a visual editor engine for editing page styles and content in the browser.

Template Structure

The template source code is located in the src/ directory with the following structure:

src/
├── assets/                          # Static resource files
│   └── img/
│       ├── QuickUI-A.svg            # QuickUI logo SVG
│       ├── QuickUI-A-base64.txt
│       ├── lufei.png                # Sample image
│       └── lufei-base64.txt
├── components/                      # React component library
│   ├── Ohters/
│   │   ├── DemoContent.tsx          # Mouse penetration demo component
│   │   └── UEConnect-Demo/
│   │       └── index.tsx            # UE5 connection demo component
│   ├── framer-motion/
│   │   ├── animated-layout.tsx      # Animation layout wrapper (motion.div)
│   │   └── animated-outlet.tsx      # Route outlet animator (AnimatePresence)
│   └── screen-anchor/
│       └── index.tsx                # Nine-grid screen positioning component
├── lib/
│   ├── data/
│   │   └── animate-data.tsx         # framer-motion default animation presets
│   └── utils.ts                     # Utility functions (cn helper)
├── pages/
│   ├── route-template/              # 【Currently Active】Routing architecture template
│   │   ├── pages/
│   │   │   ├── index.tsx            # Application entry (MemoryRouter)
│   │   │   ├── layout.tsx           # Root layout component
│   │   │   ├── error-page.tsx       # Route error boundary
│   │   │   ├── home/
│   │   │   │   └── index.tsx        # Home page - ScreenAnchor demo
│   │   │   └── show/
│   │   │       └── index.tsx        # Show page - Image loading mode demo
│   │   └── router/
│   │       └── index.tsx            # Route configuration
│   └── template/                    # Legacy flat structure (no longer active)
│       ├── index.tsx
│       └── App.tsx
├── styles/
│   └── index.css                     # Tailwind CSS directives
└── types/
    └── @type.d.ts                    # TypeScript type declarations

public/
├── audios/
│   └── light-on.mp3                 # Sample audio resource (non-inline reference)
├── img/
│   └── lufei.png                    # Sample image resource (non-inline reference)
└── index.html                       # HTML template

Core Architecture

Routing System (React Router v7 + MemoryRouter)

The template uses createMemoryRouter — navigation is entirely managed by in-memory routing.

Entry Point: src/pages/route-template/pages/index.tsx

import React from 'react'
import { createRoot } from 'react-dom/client'
import { RouterProvider, createMemoryRouter } from 'react-router'

import { router_path } from '@/pages/route-template/router'
import { UEProvider } from 'ue-connect'

const router = createMemoryRouter(router_path)

const container = document.getElementById('root')
const root = createRoot(container)
root.render(
  <>
    <UEProvider>
      <RouterProvider router={router} />
    </UEProvider>
  </>
)

Key Features:

  • UEProvider wraps the entire route tree, ensuring all child components have access to the UE5 connection context
  • createMemoryRouter creates an in-memory router instance, with routes defined by the router_path array
  • Route switching is done via useNavigate() programmatic navigation or <Link> components

Route Configuration: src/pages/route-template/router/index.tsx

export const router_path = [
  {
    path: '/',
    element: <Layout Fit={false} />,
    errorElement: <ErrorPage />,
    children: [
      { path: 'show', element: <ShowPage /> },
      { path: '/', element: <HomePage /> }
    ]
  }
]

Route hierarchy:

  • Root route /: Uses Layout as the layout component with optional auto-scaling
  • Child routes: / renders HomePage, /show renders ShowPage
  • Error boundary: Renders ErrorPage on routing errors

Page Transition Animation System

The template integrates framer-motion v11 for page transition animations.

AnimatedOutlet: src/components/framer-motion/animated-outlet.tsx

Replaces React Router's <Outlet />, using AnimatePresence to wrap child route elements for enter/exit animations:

import { AnimatePresence } from 'framer-motion'
import { cloneElement } from 'react'
import { useLocation, useOutlet } from 'react-router'

export default function AnimatedOutlet() {
  const location = useLocation()
  const element = useOutlet()
  return (
    <AnimatePresence mode="wait" initial={true}>
      {element && cloneElement(element, { key: location.pathname })}
    </AnimatePresence>
  )
}
  • Uses mode="wait": waits for the current page exit animation to complete before entering the new page
  • Drives AnimatePresence route change detection via key={location.pathname}

AnimatedLayout: src/components/framer-motion/animated-layout.tsx

Page content animation wrapper that injects framer-motion variants into child elements:

export default function AnimatedLayout({ animate, children }: Props) {
  return (
    <motion.div
      variants={animate ? animate : default_animate}
      initial="hidden"
      animate="enter"
      exit="exit"
      className="relative"
    >
      {children}
    </motion.div>
  )
}
  • Each page root element wraps content with <AnimatedLayout> for animation capabilities
  • Accepts custom animate config, defaults to default_animate presets

Animation Presets: src/lib/data/animate-data.tsx

export const default_animate = {
  hidden:  { y: -10, opacity: 0 },
  enter:   { y: 0, opacity: 1, transition: { duration: 0.5, type: 'easeInOut' } },
  exit:    { y: -50 + Math.floor(Math.random() * 30) + 1, opacity: 0,
             transition: { duration: Math.random() * 0.1 + 0.5, type: 'easeInOut' } }
}

The exit animation includes random offsets, giving consecutive page transitions a more varied feel.

ScreenAnchor Positioning Component System

src/components/screen-anchor/index.tsx provides a nine-grid positioning component set for precisely placing elements in UE5 fullscreen UI.

AnchorGrid

Container component that creates a relative-positioned fullscreen container:

export function AnchorGrid({ children }: { children: ReactNode }) {
  return <div className="select-none relative h-screen">{children}</div>
}

ScreenAnchor

Positioning anchor component that fixes child elements to one of nine preset screen positions:

type AnchorName =
  | 'top-left' | 'top-center' | 'top-right'
  | 'center-left' | 'center' | 'center-right'
  | 'bottom-left' | 'bottom-center' | 'bottom-right'

export function ScreenAnchor({ name, children, className = '' }: ScreenAnchorProps) {
  if (!children) return null
  return <div className={`${anchorStyles[name]} ${className}`}>{children}</div>
}

Nine-Grid Position Style Mapping:

AnchorNamePositioning Style
top-leftabsolute top-0 left-0
top-centerabsolute top-0 left-1/2 -translate-x-1/2
top-rightabsolute top-0 right-0
center-leftabsolute top-1/2 left-0 -translate-y-1/2
centerabsolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2
center-rightabsolute top-1/2 right-0 -translate-y-1/2
bottom-leftabsolute bottom-0 left-0
bottom-centerabsolute bottom-0 left-1/2 -translate-x-1/2
bottom-rightabsolute bottom-0 right-0

Usage example:

<AnchorGrid>
  <ScreenAnchor name="center">
    <DemoContent />
  </ScreenAnchor>
  <ScreenAnchor name="top-center">
    <div>Top Center Content</div>
  </ScreenAnchor>
</AnchorGrid>

Page Components

Root Layout: src/pages/route-template/pages/layout.tsx

The application root layout component responsible for:

  • Initializing autofit.js auto-scaling (controlled via Fit prop)
  • Calling useQuickUIEventListener('UEcallback') to enable UE5 event listening
  • Using AnimatedOutlet to render child routes with animated transitions
  • Hosting global components independent of route animations (e.g., UEConnect-Demo)
export function Layout({ Fit = false }: { Fit?: boolean }) {
  useQuickUIEventListener('UEcallback', (event) => {})
  useEffect(() => {
    if (Fit)
      autofit.init({
        dw: 1920, dh: 1080, el: 'body',
        resize: true, transition: 0.25, limit: 0.3
      })
  }, [])
  return (
    <>
      <AnimatedOutlet />
      <div id="no-animation-driven">
        <UEConnectDemo />
      </div>
    </>
  )
}

The layout separates the "animation-driven area" (AnimatedOutlet) from the "non-animation-driven area" (#no-animation-driven), ensuring global floating components are unaffected by page transition animations.

Home Page: src/pages/route-template/pages/home/index.tsx

A complete ScreenAnchor positioning component demo page, showcasing all nine anchor positions.

UE5 Route Navigation Events

Both the Home page and ShowPage listen for route navigation events from UE5:

useQuickUIEventListener('RouteNavigate', (event: QuickEvent) => {
  const payload = event.payload
  if (payload.route) {
    navigate(payload.route)
  }
})

UE5 can control frontend page switching by sending RouteNavigate events (with a route field), enabling bidirectional navigation.

Show Page: src/pages/route-template/pages/show/index.tsx

Demonstrates two image loading modes:

  1. Base64 Inline Mode: Converts small images to Base64 strings embedded in the HTML, requiring no additional loading
  2. Static Resource Mode: References images from the public/ directory via relative paths

This comparison helps developers choose the optimal image loading strategy for their use case.

Error Page: src/pages/route-template/pages/error-page.tsx

Route error boundary component that catches routing exceptions and displays error information along with the current path, providing a link back to the home page.

UEConnect-Demo Component: src/components/Ohters/UEConnect-Demo/index.tsx

A complete reference component demonstrating the core ue-connect API usage:

  • useInputBlocker: Disables right-click context menu and Tab key
  • useUEContext: Retrieves UE5 connection status, device type, mouse state, and last key action
  • useQuickUIEventSender: Sends custom events to UE5
  • useQuickUIEventListener: Listens for events from UE5
  • data-nohit attribute: Marks elements as non-penetrable for mouse events, ensuring proper event routing between Web UI and UE5

This component renders inside the #no-animation-driven container by default, acting as a global floating panel during page transitions.

DemoContent Component: src/components/Ohters/DemoContent.tsx

A mouse penetration/non-penetration comparison demo component demonstrating the data-nohit attribute in action:

  • Red area with data-nohit: Mouse events do not penetrate to UE5
  • Green area without data-nohit: Mouse events penetrate to the UE5 layer

Build Configuration

rspack.config.js

The project uses Rspack (a high-performance Rust-based web build tool) as its core bundler.

Entry Configuration:

entry: {
  index: './src/pages/route-template/pages/index.tsx',
}

src/pages/route-template/pages/index.tsx is the single entry point for the current template.

Module Resolution & Aliases:

resolve: {
  extensions: ['.js', '.jsx', '.ts', '.tsx'],
  alias: {
    '@': path.resolve(__dirname, './src'),
    'ue-connect': path.resolve(__dirname, './ue-connect')
  }
}
  • @ alias simplifies source imports
  • ue-connect alias ensures the module reference points to the local ue-connect/ directory

txt File Loading:

{
  test: /\.txt$/,
  use: 'raw-loader'
}

Supports importing .txt files as strings (e.g., Base64-encoded image data).

Dev Server:

devServer: {
  open: true,
  static: [
    { directory: path.join(__dirname, 'dist') },
    { directory: path.join(__dirname, 'public'), publicPath: '/', serveIndex: true }
  ],
  hot: true,
  historyApiFallback: true,
  port: 3000
}

Static resources in the public/ directory are served directly through the dev server, allowing relative path references to images, audio, and other assets during development.

ue-connect Module

ue-connect is the core UE5 connector module located in the ue-connect/ directory. It provides a complete set of hooks and context management tools for establishing bidirectional communication between React and UE5. For detailed documentation, please visit the ue-connect documentation

clickdeck-core Module

ClickDeck Core is a browser-based visual editor core module designed specifically for the Vibe Coding workflow. ClickDeck Core allows developers to directly drag, drop, modify styles, and adjust layouts of page DOM elements in the browser, transforming vague "interface feel" into precise "visual annotations." It is not just a debugging tool, but also the "eyes" and "translator" for AI—all visual editing operations are converted into structured AI Prompt prompts in real time. You only need to "point and click" like a designer, and leave the layout logic and code implementation to AI. clickdeck-core is a visual editor engine module added in v1.2.5, located in the clickdeck-core/ directory.

assets-tool Utilities

The assets-tool/ directory contains two build helper utilities for optimizing the final output for UE5 integration.

Tool Structure

assets-tool/
├── convertImageToBase64.js   # Image to Base64 converter
└── merge-html.js             # HTML inlining tool

convertImageToBase64

Supports converting images (PNG/JPG) to Base64-encoded text files, making it easy to embed assets directly in UE5.

⚠ Note: QuickUI supports two image resource loading methods: inline and non-inline. It is recommended to use Base64 encoding to inline small image resources into HTML files.
For non-inline resources, additional image assets need to be packaged when building for UE5. For details, please refer to Load the Built HTML

Usage:

# Convert a single file
node assets-tool/convertImageToBase64.js ./src/assets/img/lufei.png

# Batch convert all images in a directory
node assets-tool/convertImageToBase64.js ./src/assets/img/

Corresponding npm script:

"base64img": "node assets-tool/convertImageToBase64.js ./src/assets/img/"

How it works:

  • Reads the binary data of the image/WASM file
  • Converts to a Base64 string with a data:image/png;base64, or data:application/wasm;base64, prefix
  • Generates a filename-base64.txt file in the same directory
  • Supported formats: .png, .jpg, .jpeg

merge-html

An HTML inlining tool that inlines all external JS, CSS, images, and font files referenced in the build output into a single self-contained HTML file, suitable for loading in UE5's web browser widget.

Usage:

node assets-tool/merge-html.js

Corresponding npm script:

"merge-html": "node assets-tool/merge-html.js"

Use case:

  • After rspack build, the generated HTML with external resources is processed by this tool
  • Produces a single self-contained HTML file ready for loading in UE5 with no external dependencies

Using the Template

Development

Start the development server:

npm run dev

This launches a local dev server with hot module replacement for rapid development.

Building

Build the project for production:

npm run build
npm run merge-html

The build output will be generated in the dist/merged/ directory as a single HTML file with all assets inlined, ready for use in UE5.

UE5 Integration

After building, the generated HTML file can be loaded in UE5 using the QuickUI plugin's web browser widget, enabling seamless bidirectional communication between React and UE5. UE5 can control frontend page routing by sending custom events (e.g., RouteNavigate), enabling UE5-driven UI navigation.