Building a Custom Template Engine using ReactJS
MARCH 11, 2026 | JOHN NYINGI
📕This is a technical implementation guide. We are building a production-ready template editor using React, ShadCN, Tailwind and TypeScript. No fluff, just code
Table of Content
Build a Custom Template Engine using ReactJS
Technical Objectives
State Driven - Synchronization: Managing a dual-pane architecture where the Editor and Preview stay in perfect sync.
Dynamic Asset Injection: Implementing a system to load and apply text styling on the fly without page refresh.
Component Composition: Building a UI of reusable primitives using Tailwind CSS and ShadCN
Persistence & Export Logic: Saving changes and exporting the card to an image.
Final Product
Design Dashboard: A dashboard with a design sidebar and a preview in the main window.
Live Preview: Changes made in the design sidebar reflect immediately on the preview component
Asset Manager: An interactive image uploader, with error handling.
Export Module: A function to “Download Card”, converts React Components to an image.
Tech Stack
Package Manager: npm v10+
Framework: ReactJS v19 (Vite)
Language: Typescript
Styling: Tailwind
UI Components: ShadCN
Icons: Lucide-React
Project Setup
Let’s start by setting up our project. I’ll name it morph (you can morph a template into your own design). In the folder of your choice, run the following command to set up the project
npx create-rvt-app morphThis command will create a React app with Vite, Tailwind, and TypeScript preconfigured.
We will adjust a few things. The first file we will update is the vite.config.ts add the following changes to help with the file path resolution.
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path' // this is for Linux
// <https://vite.dev/config/>
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
})Note: The import
import path from 'path'is for the Linux environment, for Windows it will beimport path from 'path/win32'
Next, we will update the tsconfig.app.json to add the paths that will help resolve file imports. Inside compilerOptions Add the following
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
Locate the tsconfig.json file. Open it, and adjust the content to match the one shown below.
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
],
// add this
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}What the compilerOptions is doing is setting the @ symbol so that the compiler can resolve our paths.
Next, we will install shadcn, which is a UI component library. It speeds up development by allowing you to use reusable components such as buttons and tables.
npx shadcn@latest init
All the shadCN components will be located in the src/components/ui folder. You don’t need to create the folder it’ll be automatically created for you.
You will be prompted to install the package; hit enter. Then another prompt will ask you to choose between Radix and Base. Choose the latter (Base). You’ll then be prompted to choose a preset for this project. Choose Vega
Let’s set up fonts that we will use for the template. We are going to use Google Fonts. I went ahead and preconfigured the ideal fonts for us to use. In our index.html file, add the following to the <head> section
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Open+Sans:wght@400;500;600;700&family=Spectral:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&display=swap" rel="stylesheet">This Google Fonts link contains the following fonts: Inter, OpenSans, Spectral, and Nunito.
Next, we will create tailwind.config.ts file. Here we will define our new fonts, which we will configure for the project. Also note, we are defining the content, which is a series of file paths for Tailwind to resolve.
import type { Config } from 'tailwindcss'
export default {
content: [
"./index.html",
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
],
theme: {
extend: {
fontFamily: {
spectral : ['Spectral', 'serif'],
inter : ['Inter', 'sans-serif'],
nunito : ['Nunito', 'sans-serif'],
opensans : ['"Open Sans"', 'sans-serif'],
},
},
},
plugins: [],
} satisfies ConfigNext, we will include the tailwind.config.ts file in the components.json file. Look for the tailwind JSON object and edit it to match the one below.
Lastly, we need to make sure our index.css has the same config file included. Since we already added shadcn, now is the best time to make sure it’s well registered. Your header declaration should match the one below.
Note in the newer versions of react some imports we’re deprecated.
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@import "@fontsource-variable/inter";
@config "../tailwind.config.ts"; /* Ensure the path points to your config */
@tailwind utilities;Now we can add routes. React has a great library for managing routes: react-router-dom, which we will use to manage our routes. Going back to our terminal, run the following command.
npm i react-router-dom@latestNow we will edit our App.tsx as it’s our entry point, we change it and make it the routing component. Let’s replace the existing code with the following.
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
export default function App() {
return (
<Router>
<Routes>
</Routes>
</Router>
);
}
Now we have the necessary building blocks for the app.
In the next chapter, we will start by building our ViewPage, which will house the template content.
follow me on X @jmost_



