Migrate a Figma Export to Sistent
Turn a Figma export into a responsive, themed Sistent React component.
Introduction
30 Second Summary
A design export freezes an interface at one perfect moment. The real test begins when someone resizes the window or clicks a control.
In this project, you will turn one Figma export into a local React migration lab using Sistent. The original stays visible beside a responsive recreation with theme controls and a working review state.
What You'll Build
You will open the finished lab in Microsoft Edge to see your original export beside a responsive Sistent recreation.
By the end of this project, you'll have:
- A side-by-side migration lab that shows your original PNG beside the interface you coded.
- Working theme controls that switch the recreation between light and dark modes. A review action changes the button label to Migration reviewed when you click it.
- Responsive migration proof that stacks the comparison on a narrow screen without horizontal overflow. A production build that validates the package imports and application code.
- Secret Mission: Turn the migrated card into a reusable data-driven pattern that renders a second state with an independent review button.
Are there any prerequisites?
You need one PNG export or screenshot from your existing design folder. No prior React experience is required.
Before We Start
A clear visual reference keeps your design system migration focused on patterns that matter across the original design folder. This checkpoint commits you to one representative PNG before the hands-on work begins.
Set Up the Migration Lab
A static PNG can show the visual decisions you want to preserve. It needs a compatible React environment before those decisions can become reusable interface code.
You will create a separate workspace with Vite and the Sistent Design System. You will finish with a starter page running locally in Microsoft Edge.
In this step, get ready to:
- Confirm that Node.js and Microsoft Edge are ready.
- Create the separate workspace with its pinned project files.
- Install the dependencies and run the starter page.
Check the tools and create the workspace
Vite requires a compatible Node.js version. Checking the runtime first prevents compatibility errors during installation.
- Press the Windows key to open Windows search.
- Type Visual Studio Code into the search field.
- Press Enter to open Visual Studio Code.
- Open an integrated PowerShell terminal in Visual Studio Code.
- Check the installed Node.js version by running this command:
node --version
What Does This Check?
This command prints the Node.js version available to PowerShell. Vite requires Node.js 20.19+ or 22.12+.
✔️ I see version 20.19+ or 22.12+
Your Node.js version satisfies Vite's requirement. The runtime is ready for the migration lab.
ⓧ I see an older version
The installed version is below Vite's requirement. Update Node.js before you create the project.
- Visit the official Node.js website.
- Download a Windows installer that provides Node.js 20.19+ or 22.12+.
- Close Visual Studio Code.
- Run the downloaded installer.
- Reopen Visual Studio Code after the installation finishes.
- Open a fresh integrated PowerShell terminal.
- Confirm the updated version by running this command:
node --version
What Should You See?
PowerShell should print a version that satisfies 20.19+ or 22.12+. A fresh terminal loads the updated installation.
Still Seeing the Older Version?
Close every Visual Studio Code window after the installer finishes. Restart Windows if a fresh PowerShell terminal still reports the previous version.
Help me diagnose why PowerShell still reports my older Node.js version.
ⓧ Command not found
PowerShell cannot find Node.js. Install the runtime before continuing.
- Visit the official Node.js website.
- Download a Windows installer that provides Node.js 20.19+ or 22.12+.
- Close Visual Studio Code.
- Run the downloaded installer.
- Reopen Visual Studio Code after the installation finishes.
- Open a fresh integrated PowerShell terminal.
- Confirm the installation by running this command:
node --version
What Should You See?
PowerShell should print a version that satisfies 20.19+ or 22.12+. This confirms that the runtime is available.
Still Cannot Find Node.js?
Close every Visual Studio Code window before checking again. Restart Windows if the command remains unavailable in a fresh terminal.
Help me troubleshoot why PowerShell cannot find Node.js.
That compatibility check is complete. Your computer can now run the pinned Vite toolchain.
The final test uses Microsoft Edge. Confirming the browser now keeps the first server check focused on your application.
- Press the Windows key to open Windows search.
- Type Microsoft Edge into the search field.
- Press Enter to open Microsoft Edge.
Microsoft Edge Unavailable?
Install Microsoft Edge from the official download page. Reopen the browser from Windows search after the installation finishes.
Help me troubleshoot why Microsoft Edge does not open.
The migration lab belongs beside your existing design folder. This separation keeps the original visual assets untouched.
- Open File Explorer from Windows search.
- Return to the directory that contains your existing design folder.
- Create a sibling folder named sissent-migration-lab.
- Switch back to Visual Studio Code.
- Use the folder-opening command to select sissent-migration-lab.
- Confirm that you trust the folder if Visual Studio Code requests workspace trust.
You should see sissent-migration-lab as an empty workspace in the Explorer sidebar. Your original design folder remains unchanged.
Add the pinned project files
The project needs a package definition plus a small browser shell. The React entry point connects that shell to the starter component.
Why Use a Separate React App?
A consuming app keeps your attention on mapping the visual into Sistent components. Modifying the Sistent library would add contributor workflows before you could test the migration.
Standalone HTML could reproduce the pixels. This React app can demonstrate shared components plus theme behavior in later steps.
- Create package.json inside the sissent-migration-lab folder using the Explorer sidebar.
- Add the pinned package definition by pasting this code into package.json.
{
"name": "sistent-migration-lab",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@sistent/sistent": "0.24.3",
"react": "19.3.1",
"react-dom": "19.3.1"
},
"devDependencies": {
"@vitejs/plugin-react": "6.2.1",
"vite": "8.3.5"
}
}
What Does This File Define?
- The scripts connect the development and production commands to Vite.
- The dependencies pin Sistent to 0.24.3.
- React plus React DOM are pinned to 19.3.1.
- Vite is pinned to 8.3.5.
- The React plugin is pinned to 6.2.1.
- Save package.json.
- Confirm that package.json appears in the Explorer sidebar.
Is package.json Marked as Invalid?
Check the quotation marks around each property. Confirm that every property except the last one in an object ends with a comma.
Help me locate a syntax problem in package.json.
- Create vite.config.js inside the sissent-migration-lab folder using the Explorer sidebar.
- Configure the React plugin by pasting this code into vite.config.js.
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [react()],
})
What Does This Configuration Do?
The configuration loads Vite's React plugin. The plugin lets Vite process the JSX used by the application.
- Save vite.config.js.
- Confirm that vite.config.js appears beside package.json in the Explorer sidebar.
Are the Imports Underlined?
The imports can remain unresolved until the dependencies are installed. Check that the package names match the code block.
Help me check the imports in vite.config.js.
- Create index.html inside the sissent-migration-lab folder using the Explorer sidebar.
- Create the browser shell by pasting this code into index.html.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="A side-by-side Figma to Sistent design system migration lab"
/>
<title>Sistent Migration Lab</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
What Does This Page Provide?
The root element gives React a place to render the interface. The module script loads the entry point from src/main.jsx.
- Save index.html.
- Confirm that index.html appears beside the configuration files in the Explorer sidebar.
Does the HTML Look Incomplete?
Check that every opening HTML tag has a matching closing tag. Confirm that the module script points to /src/main.jsx.
Help me find a missing tag in index.html.
The browser shell now has its rendering target. The files inside src provide the starter output plus its styles.
- Create a folder named src inside sissent-migration-lab using the Explorer sidebar.
- Confirm that src appears beneath the project files in the Explorer sidebar.
- Create App.jsx inside the src folder using the Explorer sidebar.
- Create the starter component by pasting this code into src/App.jsx.
export default function App() {
return <p>Migration Lab Ready</p>
}
What Does This Component Render?
The App component returns one paragraph. Its message provides visible proof that React rendered successfully.
- Save src/App.jsx.
- Confirm that App.jsx appears inside src in the Explorer sidebar.
Is the Component Underlined?
Check the braces around the function body. Confirm that the paragraph has matching opening and closing tags.
Help me find a JSX syntax problem in src/App.jsx.
- Create index.css inside the src folder using the Explorer sidebar.
- Add the starter styles by pasting this code into src/index.css.
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: Arial, sans-serif;
}
#root {
min-height: 100vh;
}
What Do These Styles Establish?
The universal selector makes element sizing predictable. The body rule removes the browser's default margin.
The font keeps the starter page readable. The root element can fill the viewport height.
- Save src/index.css.
- Confirm that index.css appears beside App.jsx in the Explorer sidebar.
Are the Styles Marked as Invalid?
Check that each selector has matching braces. Confirm that each declaration has a property and a value.
Help me find a CSS syntax problem in src/index.css.
- Create main.jsx inside the src folder using the Explorer sidebar.
- Connect React to the browser shell by pasting this code into src/main.jsx.
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import './index.css'
const root = createRoot(document.getElementById('root'))
root.render(<App />)
What Does the Entry Point Do?
The entry point imports createRoot from React DOM. It also imports the starter component plus the shared stylesheet.
React finds the root element from index.html. It renders App into that element.
- Save src/main.jsx.
- Confirm that main.jsx appears beside the other files inside src.
Are the Imports Unresolved?
The package import can remain unresolved until the dependencies are installed. Check that App.jsx plus index.css use the same capitalization shown in the imports.
Help me diagnose unresolved imports in src/main.jsx.
All six project files are now present. Use the tabs below to compare the complete starter state before installation.
✔️ Awesome, I've got everything!
Your starter files are ready. Confirm that every editor tab is saved before continuing.
ⓧ I'd like to double check the full code
Compare each saved file with the complete starter code below.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="A side-by-side Figma to Sistent design system migration lab"
/>
<title>Sistent Migration Lab</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
{
"name": "sistent-migration-lab",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@sistent/sistent": "0.24.3",
"react": "19.3.1",
"react-dom": "19.3.1"
},
"devDependencies": {
"@vitejs/plugin-react": "6.2.1",
"vite": "8.3.5"
}
}
export default function App() {
return <p>Migration Lab Ready</p>
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: Arial, sans-serif;
}
#root {
min-height: 100vh;
}
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import './index.css'
const root = createRoot(document.getElementById('root'))
root.render(<App />)
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [react()],
})
Install dependencies and run the starter
The project files describe the application. npm now reads package.json to install the exact package versions required by the lab.
- Return to the integrated PowerShell terminal.
- Confirm that the prompt points to sissent-migration-lab.
The first installation can take a few minutes while npm downloads React plus Sistent. Terminal activity means the installation is progressing.
- Install the pinned project dependencies by running this command:
npm install
What Does This Command Install?
npm reads every exact version from package.json. It creates the local dependency tree that Vite uses to run the project.
- Wait for PowerShell to return to its prompt.
- Confirm that package-lock.json appears in the Explorer sidebar.
- Confirm that node_modules appears in the Explorer sidebar.
The returned prompt confirms that the installation completed. The generated files show that npm resolved the pinned dependency tree.
Did the Installation Fail?
Confirm that PowerShell points to sissent-migration-lab. npm cannot find the package definition from another folder.
Check package.json for a missing comma or quotation mark if the terminal reports invalid JSON.
Help me troubleshoot the npm installation failure.
The development command keeps PowerShell busy while Vite serves the app. Keep that terminal running during the browser check.
Before you start the server, what do you expect the browser to show when React renders the starter component?
- Start the Vite development server by running this command:
npm run dev
What Does the Development Command Do?
The script starts Vite's local development server. The terminal displays http://localhost:5173 as the local address.
The server keeps using the current PowerShell terminal. Press Ctrl+C when you eventually need to stop it.
- Return to Microsoft Edge.
- Select the browser address bar.
- Enter http://localhost:5173.
- Press Enter to load the development app.
You should see Migration Lab Ready on the page. This proves that Vite loaded the entry point plus React rendered the starter component.
Do You See a Blank Page?
Check that the PowerShell terminal is still running the development server. Confirm that Microsoft Edge is using the exact local address.
Check that src/App.jsx contains the starter message. Confirm that src/main.jsx imports App.jsx with matching capitalization.
Help me troubleshoot why the starter message is missing.
That is your first working migration surface. Next, you will place the selected PNG beside its empty implementation target.
Put the Reference Beside the Migration Target
The starter React lab now proves your local environment works. A migration also needs a visible source of truth for every layout decision.
In this step, you will copy one representative Figma export into the lab. You will display it beside an empty Sistent implementation panel in Microsoft Edge.
In this step, get ready to:
- Copy a representative PNG into the migration lab.
- Build a comparison shell around the reference image.
- Style a responsive side-by-side workspace.
Copy the reference image
A reference image gives you a stable target for hierarchy. A separate copy keeps the original design folder untouched.
- Open the existing design folder in File Explorer.
- Select one representative PNG from the folder.
- Press Ctrl+C to copy the PNG.
- Switch back to Visual Studio Code.
- Select the sissent-migration-lab folder in the Explorer sidebar.
- Select the new-folder icon in the Explorer toolbar.
- Type public as the folder name.
- Press Enter to create the folder.
Why Copy the Reference?
The migration lab uses its own copy of the export. Your original design folder remains unchanged throughout the project.
- Select the new public folder in the Explorer sidebar.
- Press Ctrl+V to paste the copied PNG.
- Select the copied PNG in the Explorer sidebar.
- Press F2 to rename the file.
- Type design-reference.png as the new name.
- Press Enter to save the new name.
The copied image now has a predictable public address. Checking that address proves the development server can find the file.
- Switch back to Microsoft Edge.
- Select the browser address bar.
- Enter http://localhost:5173/design-reference.png.
- Press Enter to load the image.
You should see your selected PNG by itself in Microsoft Edge. This confirms the export is available at /design-reference.png.
Reference Image Not Loading?
Confirm the Explorer sidebar shows public/design-reference.png. Check that the filename uses the .png extension.
If the image still does not load, help me check why my reference PNG is unavailable.
Build the comparison shell
The comparison shell separates the static source from the interface you will migrate. Its JSX creates one labelled panel for each side of that comparison.
- Switch back to Visual Studio Code.
- Select src/App.jsx in the Explorer sidebar.
- Press Ctrl+A to select the starter component.
- Replace the selected component with this comparison shell:
export default function App() {
return (
<main className="app-shell">
<header className="toolbar">
<div>
<p className="eyebrow">Sistent migration lab</p>
<h1>Reference to reusable UI</h1>
</div>
</header>
<section className="comparison-grid" aria-label="Design migration comparison">
<article className="panel">
<div className="panel-heading">
<span>Reference</span>
<span>Figma export</span>
</div>
</article>
<article className="panel">
<div className="panel-heading">
<span>Sistent implementation</span>
<span>Pending</span>
</div>
</article>
</section>
</main>
)
}
What Does This Code Do?
- The app-shell element provides the outer page boundary.
- The comparison-grid section groups the two sides of the migration.
- Each panel article gives one side a clear label.
- Save src/App.jsx.
- Switch back to Microsoft Edge.
- Return to http://localhost:5173.
- Refresh the page.
You should see Reference to reusable UI above the two panel labels. The previous starter message is gone.
Comparison Labels Missing?
Check that each opening JSX tag has a matching closing tag. Confirm the component starts with export default function App().
If the page shows an error, help me find the JSX structure problem.
The first panel now has a destination for the copied reference. An image element connects that panel to the PNG served from public.
- Switch back to src/App.jsx.
- Find the closing </div> directly below Figma export.
- Add this image element immediately below that closing tag:
<img
className="reference-image"
src="/design-reference.png"
alt="Original Figma export used as the migration reference"
/>
How Does the Image Connect?
- The src value points to the copied image's public address.
- The reference-image class provides a styling hook for the next substep.
- The alt value describes the visual reference for assistive technology.
- Save src/App.jsx.
- Switch back to Microsoft Edge.
- Refresh the page.
You should see your PNG below the Reference label. The Sistent implementation panel remains empty.
Image Missing From the Panel?
Confirm the image path is /design-reference.png. Check that the image element sits inside the first panel article.
If you see a broken image symbol, help me connect the copied PNG to the reference panel.
✔️ Awesome, I've got everything!
Your comparison shell now displays the copied reference. Confirm that src/App.jsx is saved.
ⓧ I'd like to double check the full code
export default function App() {
return (
<main className="app-shell">
<header className="toolbar">
<div>
<p className="eyebrow">Sistent migration lab</p>
<h1>Reference to reusable UI</h1>
</div>
</header>
<section className="comparison-grid" aria-label="Design migration comparison">
<article className="panel">
<div className="panel-heading">
<span>Reference</span>
<span>Figma export</span>
</div>
<img
className="reference-image"
src="/design-reference.png"
alt="Original Figma export used as the migration reference"
/>
</article>
<article className="panel">
<div className="panel-heading">
<span>Sistent implementation</span>
<span>Pending</span>
</div>
</article>
</section>
</main>
)
}
How to Use This Reference
Compare the nesting in your file with this complete component. The image belongs inside the first article.
Style the responsive comparison
The shell now contains the correct content. Its CSS will contain the image inside a readable panel.
The layout begins as one column for narrow screens. A wide window gives the reference and migration target one column each.
- Switch back to Visual Studio Code.
- Select src/index.css in the Explorer sidebar.
- Press Ctrl+A to select the existing styles.
- Replace the selected styles with these page foundations:
* {
box-sizing: border-box;
}
html {
min-width: 320px;
}
body {
margin: 0;
}
#root {
min-height: 100vh;
}
.app-shell {
min-height: 100vh;
padding: clamp(1rem, 4vw, 3rem);
}
What Do These Foundations Control?
- The universal selector keeps padding inside each element's calculated size.
- The html rule establishes the smallest supported page width.
- The app-shell rule gives the page responsive outer spacing.
- Save src/index.css.
- Switch back to Microsoft Edge.
- Refresh the page.
You should see the page content move away from the window edges. The outer spacing changes with the viewport width.
Page Still Touching the Edges?
Check that .app-shell includes its padding declaration. Confirm the main element uses className="app-shell".
If the spacing still does not appear, help me connect the app-shell styles to the page.
- Return to the end of src/index.css.
- Add these alignment styles below the existing rules:
.toolbar,
.comparison-grid {
width: min(100%, 1200px);
margin-left: auto;
margin-right: auto;
}
.toolbar {
margin-bottom: 1.5rem;
}
.eyebrow {
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
}
How Does the Header Stay Aligned?
- The toolbar shares its maximum width with the comparison grid.
- Automatic side margins center both regions.
- The eyebrow rule turns the lab label into a compact section marker.
- Save src/index.css.
- Switch back to Microsoft Edge.
- Refresh the page.
You should see the heading align with the comparison content. The lab label now uses compact uppercase lettering.
Header Alignment Unchanged?
Check the comma between .toolbar and .comparison-grid. The shared rule must target both selectors.
If the alignment still does not change, help me check the shared CSS selector.
- Return to the end of src/index.css.
- Add these grid and panel styles below the existing rules:
.comparison-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
.panel {
min-width: 0;
padding: clamp(1rem, 3vw, 1.5rem);
border: 1px solid currentColor;
border-radius: 1rem;
overflow: hidden;
}
.panel-heading {
display: flex;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1rem;
font-size: 0.82rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
}
How Do the Panels Create Structure?
- The grid starts with one flexible column.
- Each panel receives responsive padding with a visible boundary.
- The panel heading places its two labels on opposite sides.
- Save src/index.css.
- Switch back to Microsoft Edge.
- Refresh the page.
You should see two rounded panels stacked vertically. Each panel should contain its label inside a visible border.
Panels Still Look Like Plain Text?
Confirm both article elements use className="panel". Check that both label groups use className="panel-heading".
If the borders remain missing, help me connect the panel selectors to the JSX.
The final styles contain the PNG within its panel. A responsive breakpoint places both panels side by side when the browser is wide enough.
- Return to the end of src/index.css.
- Add the image and wide-screen styles below the existing rules:
.reference-image {
display: block;
width: 100%;
max-height: 520px;
object-fit: contain;
border-radius: 0.75rem;
}
@media (min-width: 900px) {
.comparison-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
What Changes With These Styles?
- The reference image fills the available panel width while preserving its proportions.
- The maximum height prevents a tall export from taking over the page.
- The media query gives each panel an equal column on wide screens.
Before you check, ask yourself whether the panels will remain stacked or sit side by side in a wide window.
- Save src/index.css.
- Switch back to Microsoft Edge.
- Maximize the browser window.
- Refresh the page.
You should see your export under Reference with an empty Sistent implementation panel beside it. Both panels should fit within the wide window.
Panels Remain Stacked in a Wide Window?
Confirm the browser viewport is at least 900px wide. Check that the media query surrounds the comparison-grid rule.
If the panels still remain stacked, help me debug the responsive comparison grid.
✔️ Awesome, I've got everything!
Your responsive page foundations are complete. Confirm that src/index.css is saved.
ⓧ I'd like to double check the full code
* {
box-sizing: border-box;
}
html {
min-width: 320px;
}
body {
margin: 0;
}
#root {
min-height: 100vh;
}
.app-shell {
min-height: 100vh;
padding: clamp(1rem, 4vw, 3rem);
}
.toolbar,
.comparison-grid {
width: min(100%, 1200px);
margin-left: auto;
margin-right: auto;
}
.toolbar {
margin-bottom: 1.5rem;
}
.eyebrow {
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.comparison-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
.panel {
min-width: 0;
padding: clamp(1rem, 3vw, 1.5rem);
border: 1px solid currentColor;
border-radius: 1rem;
overflow: hidden;
}
.panel-heading {
display: flex;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1rem;
font-size: 0.82rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.reference-image {
display: block;
width: 100%;
max-height: 520px;
object-fit: contain;
border-radius: 0.75rem;
}
@media (min-width: 900px) {
.comparison-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
How to Use This Reference
Compare each selector with your saved stylesheet. The media query belongs at the end of the file.
That visual source of truth now sits beside a dedicated migration target. Next, you will recreate its hierarchy with an intentionally brittle fixed-width copy.
Build the Brittle Copy First
Your Figma reference is visible beside an empty implementation panel. A wide layout can make a pixel copy look finished before its limits are tested.
The next layer is a semantic React recreation with deliberately fixed CSS. You will use its narrow-screen failure as evidence of the migration problem.
In this step, get ready to:
- Build the temporary recreation with semantic React elements.
- Apply a fixed width of 680px with one-off CSS.
- Expose the horizontal overflow by narrowing Microsoft Edge.
Recreate the visual hierarchy
Semantic HTML gives each part of the temporary recreation a clear role. The status uses a label while the content uses a heading, paragraph, list, plus a native button.
- Switch back to src/App.jsx in the VS Code editor.
- Find the Reference to reusable UI heading.
- Add the supporting copy directly below that heading by pasting:
<p className="supporting-copy">
Compare the static source with a temporary fixed-width copy.
</p>
What does this copy do?
The supporting-copy class identifies descriptive text that needs consistent treatment. The sentence also tells viewers that this version is temporary.
- Save src/App.jsx.
- Return to Microsoft Edge.
You will see the new sentence beneath the page heading. The reference panel remains unchanged.
Supporting copy missing?
Check that the new paragraph sits inside the toolbar's inner div. Make sure src/App.jsx is saved.
If the browser reports a syntax problem, compare the opening plus closing paragraph tags. Help me fix the supporting paragraph in my React component.
The empty implementation panel now needs the same content hierarchy you can inspect in the reference. This first pass uses a native control so every decision remains isolated inside the copy.
- Find the second article whose panel heading ends with Pending.
- Replace that complete article with:
<article className="panel">
<div className="panel-heading">
<span>Sistent implementation</span>
<span>Temporary copy</span>
</div>
<section className="brittle-card">
<span className="status-label">Ready to migrate</span>
<h2>Design review workspace</h2>
<p className="supporting-copy">
A temporary pixel copy for exposing fixed-width layout problems.
</p>
<ul className="migration-list">
<li>Fixed visual hierarchy</li>
<li>One-off spacing</li>
<li>Native action treatment</li>
</ul>
<button type="button">Mark as reviewed</button>
</section>
</article>
What does this structure do?
- The brittle-card section groups the temporary recreation into one surface.
- The heading plus supporting copy establish the main content hierarchy.
- The migration-list names the isolated choices that this copy represents.
- The native button provides an action without a shared component system.
- Save src/App.jsx.
- Return to Microsoft Edge.
You will see the status label, heading, description, checklist, plus native button in the implementation panel. That is the temporary hierarchy you will stress-test.
Temporary card not rendering?
Check that the replacement starts with the second panel's opening article tag. Confirm that its final closing tag appears before the comparison section closes.
A blank page usually points to an unmatched JSX tag. Help me find the unmatched JSX tag in my temporary implementation panel.
✔️ Awesome, I've got everything!
Great. Save src/App.jsx once more before styling the temporary recreation.
ⓧ I'd like to double check the full code
export default function App() {
return (
<main className="app-shell">
<header className="toolbar">
<div>
<p className="eyebrow">Sistent migration lab</p>
<h1>Reference to reusable UI</h1>
<p className="supporting-copy">
Compare the static source with a temporary fixed-width copy.
</p>
</div>
</header>
<section className="comparison-grid" aria-label="Design migration comparison">
<article className="panel">
<div className="panel-heading">
<span>Reference</span>
<span>Figma export</span>
</div>
<img
className="reference-image"
src="/design-reference.png"
alt="Original Figma export used as the migration reference"
/>
</article>
<article className="panel">
<div className="panel-heading">
<span>Sistent implementation</span>
<span>Temporary copy</span>
</div>
<section className="brittle-card">
<span className="status-label">Ready to migrate</span>
<h2>Design review workspace</h2>
<p className="supporting-copy">
A temporary pixel copy for exposing fixed-width layout problems.
</p>
<ul className="migration-list">
<li>Fixed visual hierarchy</li>
<li>One-off spacing</li>
<li>Native action treatment</li>
</ul>
<button type="button">Mark as reviewed</button>
</section>
</article>
</section>
</main>
)
}
Apply the fixed-width styling
A pixel copy usually starts with isolated dimensions plus one-off styling. These rules make that weakness repeatable by locking the card to 680px.
- Switch back to src/index.css in the VS Code editor.
- Add this form-control rule directly below the existing body rule:
button,
input,
textarea,
select {
font: inherit;
}
Why inherit the font?
Form controls can use browser-specific font defaults. Inheriting the page font makes the native button follow the surrounding text before its one-off visual styles are applied.
- Find the existing .eyebrow selector.
- Replace that selector with:
.eyebrow {
margin-bottom: 0.35rem;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
}
What changes here?
The added bottom margin creates a small gap beneath the eyebrow label. The remaining declarations preserve its compact uppercase treatment.
- Save src/index.css.
- Return to Microsoft Edge.
You will see a clearer gap beneath the eyebrow. The native button now uses the same font as the page.
Typography unchanged?
Confirm the form-control selector sits outside the body rule. Check that the .eyebrow selector still has both braces.
If the stylesheet stops applying, look for a missing closing brace above these rules. Help me find the CSS syntax problem around my form controls and eyebrow styles.
- Return to src/index.css.
- Add this selector directly below the .eyebrow rule:
.supporting-copy {
max-width: 62ch;
opacity: 0.74;
line-height: 1.6;
}
What does this text style do?
The character-based maximum width keeps descriptions readable. Reduced opacity gives supporting text less visual weight than the headings.
- Save src/index.css.
- Return to Microsoft Edge.
You will see both descriptions become softer. Their line spacing will also become more open.
- Return to src/index.css.
- Delete the overflow: hidden; declaration from the existing .panel selector.
- Add the fixed card rule directly below the .reference-image selector:
.brittle-card {
width: 680px;
padding: 32px;
border: 1px solid currentColor;
border-radius: 12px;
}
Why is this card brittle?
The fixed 680px width cannot shrink with its panel. Removing the panel's overflow clipping lets the page expose that mismatch during the narrow-screen check.
- Save src/index.css.
- Return to Microsoft Edge.
You will see a bordered card with fixed internal space. Its width now ignores the space available inside the implementation panel.
Card still has no border?
Check that the stylesheet uses .brittle-card. Confirm that the React section uses className="brittle-card".
If the card stays clipped inside the panel, confirm that overflow: hidden; was removed from .panel. Help me diagnose why my brittle-card styles are missing or clipped.
The native button now needs an isolated visual treatment. Its hardcoded color plus spacing demonstrate how quickly one-off decisions accumulate.
- Add this button selector directly below the .brittle-card rule:
.brittle-card button {
padding: 12px 18px;
color: white;
background: #1f5eff;
border: 0;
border-radius: 6px;
}
What makes this styling isolated?
Every visual choice belongs only to the button inside .brittle-card. The color plus dimensions do not come from a shared theme or component API.
- Save src/index.css.
- Return to Microsoft Edge.
You will see a blue native button with white text. Its appearance now comes entirely from the one-off selector.
Button still looks unstyled?
Confirm that the button remains inside the element with className="brittle-card". Check that the selector includes a space between .brittle-card plus button.
A missing brace in the preceding card rule can also block this selector. Help me fix the native button styling inside brittle-card.
- Return to src/index.css.
- Add these finishing rules directly below the button selector:
.status-label {
display: inline-block;
padding: 0.3rem 0.65rem;
border: 1px solid currentColor;
border-radius: 999px;
font-size: 0.78rem;
font-weight: 700;
}
.migration-list {
line-height: 1.5;
}
What do these rules finish?
- The .status-label selector turns the migration state into a compact pill.
- The .migration-list selector gives the checklist more readable vertical rhythm.
- Save src/index.css.
- Return to Microsoft Edge.
You will see a rounded status label above the card heading. The checklist items will have more space between their lines.
Status label still looks plain?
Check that the React span uses className="status-label". Confirm that the selector begins with a period.
If the checklist spacing is unchanged, compare migration-list in both files. Help me compare my status-label and migration-list class names.
✔️ Awesome, I've got everything!
Your temporary recreation is complete. Save src/index.css before testing the fixed width.
ⓧ I'd like to double check the full code
* {
box-sizing: border-box;
}
html {
min-width: 320px;
}
body {
margin: 0;
}
button,
input,
textarea,
select {
font: inherit;
}
#root {
min-height: 100vh;
}
.app-shell {
min-height: 100vh;
padding: clamp(1rem, 4vw, 3rem);
}
.toolbar,
.comparison-grid {
width: min(100%, 1200px);
margin-left: auto;
margin-right: auto;
}
.toolbar {
margin-bottom: 1.5rem;
}
.eyebrow {
margin-bottom: 0.35rem;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.supporting-copy {
max-width: 62ch;
opacity: 0.74;
line-height: 1.6;
}
.comparison-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
.panel {
min-width: 0;
padding: clamp(1rem, 3vw, 1.5rem);
border: 1px solid currentColor;
border-radius: 1rem;
}
.panel-heading {
display: flex;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1rem;
font-size: 0.82rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.reference-image {
display: block;
width: 100%;
max-height: 520px;
object-fit: contain;
border-radius: 0.75rem;
}
.brittle-card {
width: 680px;
padding: 32px;
border: 1px solid currentColor;
border-radius: 12px;
}
.brittle-card button {
padding: 12px 18px;
color: white;
background: #1f5eff;
border: 0;
border-radius: 6px;
}
.status-label {
display: inline-block;
padding: 0.3rem 0.65rem;
border: 1px solid currentColor;
border-radius: 999px;
font-size: 0.78rem;
font-weight: 700;
}
.migration-list {
line-height: 1.5;
}
@media (min-width: 900px) {
.comparison-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
Expose the narrow-screen failure
The temporary copy now has enough visual structure to feel convincing at a generous browser width. Its fixed dimension becomes the problem when the available space shrinks.
- Return to the running migration lab in Microsoft Edge.
- Widen the browser window until the two panels sit beside each other.
- Compare the temporary card's heading with the hierarchy in your reference.
- Compare the temporary card's supporting text with the reference.
- Compare the native action with the reference's main control.
At this width, you can see the reference beside a complete semantic recreation. The fixed card can still look plausible when the viewport gives it enough room.
Before you narrow the window, do you think the 680px card will shrink with its panel?
- Narrow the Microsoft Edge window below the temporary card's 680px width.
You will see a horizontal scrollbar while part of the fixed-width copy is cut off. This intended failure proves that matching one wide view does not create a responsive interface.
What did the failure reveal?
The semantic content still works, but its container cannot respond to the available width. The fixed dimension turns a convincing wide-screen copy into a broken narrow-screen experience.
You have captured the exact weakness that a design system migration needs to solve. Next, you will replace the isolated copy with a responsive Sistent implementation.
Replace Visual Guesses with Sistent
You exposed the temporary recreation's fixed-width overflow in Microsoft Edge. The lab now has a concrete migration problem to solve.
This step replaces isolated visual choices with the Sistent Design System. Your React interface gains shared theme context, responsive styling, and observable interaction state.
In this step, get ready to:
- Connect the interface to Sistent's provider and active theme.
- Replace the native controls with themed Sistent buttons.
- Apply the final responsive layout and verify the migration.
Connect the Sistent theme
A theme provider shares system values with every component inside it. The useTheme() hook then exposes the active text color to the migration workspace.
Why Use a Consuming App?
Your goal is to map a visual export to an existing component system. A small consuming app keeps your attention on that migration instead of adding library builds or contributor tooling.
- Switch back to src/App.jsx in Visual Studio Code.
- Replace its existing contents with the following five sections in order.
- Add the imports and workspace state with this first section:
import { useState } from 'react'
import {
Button,
SistentThemeProvider,
useTheme,
} from '@sistent/sistent'
function MigrationWorkspace({ mode, onToggleMode }) {
const theme = useTheme()
const [reviewed, setReviewed] = useState(false)
What Does This Section Do?
- The imports make Sistent's provider, theme hook, and shared button available to the file.
- The reviewed state records whether you have completed the migration review.
- The workspace receives the active mode and its toggle function through props.
- Continue the workspace with the themed page shell and toolbar:
return (
<main
className="app-shell"
style={{ color: theme.palette.text.default }}
>
<header className="toolbar">
<div>
<p className="eyebrow">Sistent migration lab</p>
<h1>Reference to reusable UI</h1>
<p className="supporting-copy">
Compare the static source with a responsive, themed implementation.
</p>
</div>
<Button variant="outlined" onClick={onToggleMode}>
Switch to {mode === 'light' ? 'dark' : 'light'} mode
</Button>
</header>
How Does the Toolbar Use the Theme?
- The page reads theme.palette.text.default so its text follows the active Sistent theme.
- The outlined button calls onToggleMode when you click it.
- The button label calculates the next available color mode.
- Add the comparison grid and reference panel beneath the toolbar:
<section
className="comparison-grid"
aria-label="Design migration comparison"
>
<article className="panel">
<div className="panel-heading">
<span>Reference</span>
<span>Figma export</span>
</div>
<img
className="reference-image"
src="/design-reference.png"
alt="Original Figma export used as the migration reference"
/>
</article>
What Does the Reference Panel Preserve?
The reference panel keeps your copied export visible beside the implementation. This gives every component and spacing decision a stable visual source.
- Add the responsive implementation panel beneath the reference panel:
<article className="panel">
<div className="panel-heading">
<span>Sistent implementation</span>
<span>Responsive</span>
</div>
<section className="migrated-card">
<span className="status-label">
{reviewed ? 'Reviewed' : 'Ready to migrate'}
</span>
<h2>Design review workspace</h2>
<p className="supporting-copy">
A reusable surface for comparing a visual reference with its
system-based implementation.
</p>
<ul className="migration-list">
<li>Theme-aware text</li>
<li>Responsive layout</li>
<li>Shared Sistent action</li>
</ul>
<Button
variant="contained"
aria-pressed={reviewed}
onClick={() => setReviewed(true)}
>
{reviewed ? 'Migration reviewed' : 'Mark as reviewed'}
</Button>
</section>
</article>
How Does the Review State Work?
- The contained Sistent button gives the card a shared action treatment.
- The click handler changes reviewed to true.
- The status and button labels respond to that state change.
- The aria-pressed value exposes the control's state to assistive technology.
- Finish the comparison and add the provider-owned app state:
</section>
</main>
)
}
export default function App() {
const [mode, setMode] = useState('light')
function toggleMode() {
setMode((currentMode) =>
currentMode === 'light' ? 'dark' : 'light',
)
}
return (
<SistentThemeProvider initialMode={mode}>
<MigrationWorkspace mode={mode} onToggleMode={toggleMode} />
</SistentThemeProvider>
)
}
What Does the Provider Control?
- The top-level mode state starts in light mode.
- The toggleMode() function switches between light and dark values.
- The provider receives the current mode through initialMode.
- The workspace receives the same mode so its button can describe the next action.
✔️ Awesome, I've got everything!
- Save src/App.jsx.
ⓧ I'd like to double check the full code
import { useState } from 'react'
import {
Button,
SistentThemeProvider,
useTheme,
} from '@sistent/sistent'
function MigrationWorkspace({ mode, onToggleMode }) {
const theme = useTheme()
const [reviewed, setReviewed] = useState(false)
return (
<main
className="app-shell"
style={{ color: theme.palette.text.default }}
>
<header className="toolbar">
<div>
<p className="eyebrow">Sistent migration lab</p>
<h1>Reference to reusable UI</h1>
<p className="supporting-copy">
Compare the static source with a responsive, themed implementation.
</p>
</div>
<Button variant="outlined" onClick={onToggleMode}>
Switch to {mode === 'light' ? 'dark' : 'light'} mode
</Button>
</header>
<section
className="comparison-grid"
aria-label="Design migration comparison"
>
<article className="panel">
<div className="panel-heading">
<span>Reference</span>
<span>Figma export</span>
</div>
<img
className="reference-image"
src="/design-reference.png"
alt="Original Figma export used as the migration reference"
/>
</article>
<article className="panel">
<div className="panel-heading">
<span>Sistent implementation</span>
<span>Responsive</span>
</div>
<section className="migrated-card">
<span className="status-label">
{reviewed ? 'Reviewed' : 'Ready to migrate'}
</span>
<h2>Design review workspace</h2>
<p className="supporting-copy">
A reusable surface for comparing a visual reference with its
system-based implementation.
</p>
<ul className="migration-list">
<li>Theme-aware text</li>
<li>Responsive layout</li>
<li>Shared Sistent action</li>
</ul>
<Button
variant="contained"
aria-pressed={reviewed}
onClick={() => setReviewed(true)}
>
{reviewed ? 'Migration reviewed' : 'Mark as reviewed'}
</Button>
</section>
</article>
</section>
</main>
)
}
export default function App() {
const [mode, setMode] = useState('light')
function toggleMode() {
setMode((currentMode) =>
currentMode === 'light' ? 'dark' : 'light',
)
}
return (
<SistentThemeProvider initialMode={mode}>
<MigrationWorkspace mode={mode} onToggleMode={toggleMode} />
</SistentThemeProvider>
)
}
Test the themed controls
The interface now has two shared Sistent controls. One changes the color mode while the other records the review state.
- Return to Microsoft Edge.
- Click Switch to dark mode.
You should see the page text and Sistent controls respond to dark mode. The toolbar button now reads Switch to light mode.
- Click Mark as reviewed.
You should see the status change to Reviewed. The button changes to Migration reviewed.
That interaction proves the component now responds to state. The remaining brittle width and one-off button rules still need to leave the stylesheet.
- Switch back to src/index.css in Visual Studio Code.
- Select the CSS from .brittle-card through the existing .migration-list block.
- Replace the first part of that selected CSS with the responsive card rules below:
.migrated-card {
display: grid;
gap: 1rem;
padding: clamp(1rem, 4vw, 2rem);
border: 1px solid currentColor;
border-radius: 0.75rem;
}
.migrated-card h2,
.migrated-card p,
.migration-list {
margin-bottom: 0;
}
What Changed in the Card?
- The card uses a flexible grid instead of a fixed width.
- The clamp() value lets padding adapt to the viewport.
- The shared gap establishes a repeatable spacing rhythm.
- Complete the selected replacement with the status and checklist rules below:
.status-label {
width: fit-content;
padding: 0.3rem 0.65rem;
border: 1px solid currentColor;
border-radius: 999px;
font-size: 0.78rem;
font-weight: 700;
}
.migration-list {
display: grid;
gap: 0.5rem;
padding-left: 1.25rem;
line-height: 1.5;
}
Why Update These Details?
- The status label sizes itself to its content.
- The checklist uses the same grid-based spacing pattern as the card.
- Removing the native button selector leaves control styling to Sistent.
- Save src/index.css.
- Return to Microsoft Edge.
- Narrow the browser below 680px.
You should see the migrated card shrink with its panel. The fixed-width cut-off and one-off native button treatment are gone.
Still Seeing the Brittle Card?
Check that src/App.jsx uses className="migrated-card". Make sure the old .brittle-card selectors are no longer in src/index.css.
Confirm both files are saved before returning to the browser.
help me find why my migrated card still uses the old fixed-width styling
Apply the responsive comparison layout
The final layout gives the toolbar and panels clear responsive rules. A wide screen shows two comparison columns while a narrow screen stacks the interface without horizontal overflow.
- In src/index.css, replace the combined toolbar and comparison width rules with this toolbar section:
.toolbar {
width: min(100%, 1200px);
margin: 0 auto 1.5rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.toolbar h1,
.migrated-card h2,
.toolbar p {
margin-top: 0;
}
What Does the Toolbar Layout Do?
- The toolbar stays centered inside a maximum width of 1200px.
- Flexbox places the heading content beside the theme control on wider screens.
- The shared margin rule removes inconsistent space above key text elements.
- Replace the existing .comparison-grid block with the final centered grid:
.comparison-grid {
width: min(100%, 1200px);
margin: 0 auto;
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
How Does the Grid Stay Responsive?
The grid begins with one flexible column. Its media query adds the second column only when the viewport has enough room.
- Replace the existing panel rules with these final panel styles:
.panel {
min-width: 0;
padding: clamp(1rem, 3vw, 1.5rem);
border: 1px solid currentColor;
border-radius: 1rem;
overflow: hidden;
}
.panel-heading {
display: flex;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1rem;
font-size: 0.82rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.panel-heading span:last-child {
opacity: 0.62;
}
What Do the Panels Protect?
- The min-width rule lets each grid item shrink below its content's preferred width.
- The overflow rule keeps panel content inside its rounded boundary.
- The secondary panel label uses lower opacity to preserve visual hierarchy.
- Replace the existing media query with both final responsive queries:
@media (min-width: 900px) {
.comparison-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 640px) {
.toolbar {
align-items: stretch;
flex-direction: column;
}
}
How Do the Breakpoints Work?
- At 900px or wider, the reference and implementation use equal columns.
- At 640px or narrower, the toolbar stacks its content vertically.
- The layout keeps the reference and implementation readable at both sizes.
✔️ Awesome, I've got everything!
- Save src/index.css.
ⓧ I'd like to double check the full code
* {
box-sizing: border-box;
}
html {
min-width: 320px;
}
body {
margin: 0;
}
button,
input,
textarea,
select {
font: inherit;
}
#root {
min-height: 100vh;
}
.app-shell {
min-height: 100vh;
padding: clamp(1rem, 4vw, 3rem);
}
.toolbar {
width: min(100%, 1200px);
margin: 0 auto 1.5rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.toolbar h1,
.migrated-card h2,
.toolbar p {
margin-top: 0;
}
.eyebrow {
margin-bottom: 0.35rem;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.supporting-copy {
max-width: 62ch;
opacity: 0.74;
line-height: 1.6;
}
.comparison-grid {
width: min(100%, 1200px);
margin: 0 auto;
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
.panel {
min-width: 0;
padding: clamp(1rem, 3vw, 1.5rem);
border: 1px solid currentColor;
border-radius: 1rem;
overflow: hidden;
}
.panel-heading {
display: flex;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1rem;
font-size: 0.82rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.panel-heading span:last-child {
opacity: 0.62;
}
.reference-image {
display: block;
width: 100%;
max-height: 520px;
object-fit: contain;
border-radius: 0.75rem;
}
.migrated-card {
display: grid;
gap: 1rem;
padding: clamp(1rem, 4vw, 2rem);
border: 1px solid currentColor;
border-radius: 0.75rem;
}
.migrated-card h2,
.migrated-card p,
.migration-list {
margin-bottom: 0;
}
.status-label {
width: fit-content;
padding: 0.3rem 0.65rem;
border: 1px solid currentColor;
border-radius: 999px;
font-size: 0.78rem;
font-weight: 700;
}
.migration-list {
display: grid;
gap: 0.5rem;
padding-left: 1.25rem;
line-height: 1.5;
}
@media (min-width: 900px) {
.comparison-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 640px) {
.toolbar {
align-items: stretch;
flex-direction: column;
}
}
Before you check the final migration, do you expect the reference and implementation to stay side by side when the browser becomes narrow?
- Return to the development terminal from earlier.
- Confirm the existing development server is still running.
- Return to http://localhost:5173 in Microsoft Edge.
- Widen Microsoft Edge beyond 900px.
You should see the reference and Sistent implementation in two balanced columns.
- Click Switch to dark mode.
- Click Mark as reviewed.
- Narrow Microsoft Edge below 640px.
You should see the colors and controls follow the selected mode. The review button should read Migration reviewed.
The toolbar and comparison panels should stack without a horizontal scrollbar. That brittle fixed-width copy is now a responsive design system migration.
Seeing Overflow or Unchanged Controls?
Check that the old width: 680px rule and native button selector are gone. Confirm src/App.jsx imports every Sistent value shown in the full-code check.
If the layout stays in two columns on a narrow screen, compare both media queries with the final src/index.css reference.
help me debug why my final Sistent migration still overflows or does not update its controls
Your migration now responds to theme, interaction, and viewport changes. Next, you will audit those mappings and validate the integration with a production build.
Audit and Build the Migration
Your Figma reference now sits beside a responsive Sistent implementation. Light mode, dark mode, and the reviewed state all work without narrow-screen overflow.
A visual match in the React development server can still hide import or bundling errors. This step pairs a four-part audit with a Vite production build.
In this step, get ready to:
- Audit the migrated interface against the visual reference.
- Build the Vite, React, and Sistent integration as a production bundle.
- Run the final migration demo across every theme, interaction, and screen size.
Audit the four design mappings
A visual audit connects the static reference to the design decisions in your coded version. Each check reveals whether you migrated a reusable behavior instead of copying a single appearance.
What are the four mappings?
- Visual hierarchy covers the order and prominence of the status label, heading, description, checklist, and action.
- Spacing rhythm covers the gaps and padding that separate related content.
- Action treatment covers how the shared Button communicates its purpose and reviewed state.
- Narrow-screen behavior covers how the panels stack without creating horizontal overflow.
- Switch back to Microsoft Edge.
- Widen the browser window until the reference panel and implementation panel sit side by side.
- Compare the status label, heading, description, checklist, and action with the hierarchy in your PNG.
- Inspect the spacing between the implementation card elements.
- Compare the visible action in the PNG with the Sistent Button treatment.
- Narrow the browser window until the two panels stack.
- Record one deliberate difference where Sistent improves consistency over the static export in the checkpoint below.
That audit gives you a repeatable way to judge future migrations. You can now explain which visual decisions became system behavior.
Build the production bundle
The development server currently occupies the terminal in Visual Studio Code. A production build uses that terminal to compile the application and validate its imports.
- Return to Visual Studio Code.
- Stop the running development server by pressing Ctrl+C in the terminal.
Before you build, do you expect the imports that worked in development to pass production bundling?
- Create the production bundle by running this command:
npm run build
What does this command do?
The package script asks Vite to compile the React application for production. This validates the Sistent imports and the application code as one bundle.
You should see the build finish without errors. Your terminal prompt returns when the bundle is complete.
Build not finishing?
- Confirm that the terminal is using the same Visual Studio Code workspace that contains package.json.
- Review the first reported file path to locate the import or syntax issue.
- Ask for help with the first build message through help me diagnose why my production build does not finish.
That is the final technical gate cleared. Your React, Vite, and Sistent integration now bundles for production.
Run the final migration demo
The successful bundle proves that the code compiles for production. The final demo confirms that the audited behavior still works in the browser.
The development server occupies this terminal while it runs.
Before you restart the app, which theme, interaction, and layout behaviors do you expect to see?
- Restart the development server by running this command:
npm run dev
What does this command do?
The development script starts Vite so you can test the production-validated application in Microsoft Edge. It serves the local app at http://localhost:5173.
You should see the local development address in the terminal.
- Return to the Microsoft Edge tab from earlier.
- Refresh the page at http://localhost:5173.
- Widen the browser window until the panels sit side by side.
You should see your reference PNG beside the responsive Sistent implementation. The interface starts in light mode.
- Click Switch to dark mode.
You should see the interface colors update. The outlined control now offers Switch to light mode.
- Click Mark as reviewed.
You should see the action label change to Migration reviewed.
- Narrow the Microsoft Edge window until the panels stack.
You should see the panels form one column. You should see no horizontal scrollbar.
You have cleared both migration checks. The visual audit proves the design decisions while the production build proves the integration.
Secret mission
Make the Migrated Pattern Reusable
Turn your migrated interface into a reusable card pattern driven by data. Render two related cards with review buttons that update independently.
Clean Up Your Resources
Clean Up Your Resources
Everything you built lives on your Windows computer. There are no cloud resources or ongoing costs.
Decide whether to keep the migration lab running, pause it for later, or delete it entirely.
Resources you used:
- The sissent-migration-lab folder containing your React source code, copied reference image, installed dependencies, and production build.
- The local Vite development server displaying the migration lab in Microsoft Edge.
Keep everything running
No action needed. Choose this if you are still refining the migration or want to reuse the card pattern soon.
- Keep the development server running for immediate access to the migration lab.
- Keep the sissent-migration-lab folder with its source code, copied reference image, dependencies, and production build.
- Use the completed visual audit as a checklist when migrating another export.
Pause - I'll come back to this later
Shut down the development server to free up memory. Your migration lab stays on disk for the next work session.
- Switch back to the PowerShell terminal in Visual Studio Code.
- Stop the development server by pressing Ctrl+C.
- Leave the sissent-migration-lab folder untouched.
- Restart the development server later from that folder by running this command:
npm run dev
What does this command do?
The command starts the local development server so Microsoft Edge can load your migration lab again.
- Return to http://localhost:5173 in Microsoft Edge.
You should see the reference beside the Design review workspace and Developer handoff cards.
Migration Lab Not Loading?
Check that the PowerShell terminal is inside the sissent-migration-lab folder before starting the server.
Ask for help diagnosing the local server if the page still does not load.
Delete - I don't want to use this again
Deleting the migration lab permanently removes its source code, copied reference image, installed dependencies, and production build. Your original design folder stays untouched.
- Switch back to the PowerShell terminal in Visual Studio Code.
- Stop the development server by pressing Ctrl+C.
- Move to the parent folder, delete sissent-migration-lab, and list the remaining folders by running these commands:
cd ..
cmd /c rmdir /s /q sissent-migration-lab
dir
What does this cleanup do?
- The first command moves the terminal out of the project folder so Windows can remove it.
- The second command permanently deletes the sissent-migration-lab folder and everything inside it.
- The final command prints the remaining folders so you can verify the deletion.
- Review the folder listing printed by the final command.
The sissent-migration-lab folder should no longer appear in the listing.
Folder Still Listed?
Confirm that the development server has stopped. Close any application still using a file from the migration lab before running the cleanup commands again.
Ask for help removing the local folder if Windows still keeps it in use.
Nice Work!
Nice Work!
Excellent work! You transformed a static Figma export into a responsive Sistent migration lab.
You've learned how to:
- Translate a visual reference into a responsive interface with React while preserving its hierarchy across wide and narrow screens.
- Use theme context to keep shared Sistent Buttons consistent in light or dark mode. Track review state so each card responds independently.
- Run a production build to validate the React, Vite, and Sistent integration. Apply a repeatable visual audit across wide and narrow views.
- Secret Mission: Turn the migrated interface into a data-driven card pattern that renders Design review workspace and Developer handoff. Keep each card's review state independent.
Ready to quiz yourself?