Retool is a fantastic tool for building internal apps quickly, but sometimes the out-of-the-box components don’t quite cut it.
I recently worked on a project for a US-based logistics firm where we needed a very specific interactive map that wasn’t available in the standard library.
That is when I dived deep into building a custom React component for Retool to bridge that gap.
In this guide, I will show you exactly how to build your own custom components to make your Retool apps much more powerful.
Retool Custom Component Architecture
Before we start coding, it is important to understand how Retool talks to your React code.
Retool hosts your custom component in an iframe to keep the main application secure and performant.
You will primarily interact with two things: the Model (data coming from Retool) and UpdateModel (sending data back to Retool).
Method 1: Use the Retool Web Editor (Quickest Way)
If you have a relatively simple component, you can write the code directly inside the Retool web interface.
I use this method frequently when I need a specialized UI element, like a custom US State selector with specific branding.
Step 1: Drag the Custom Component
First, open your Retool app and drag a “Custom Component” from the component library onto your canvas.
You will see some boilerplate code in the “Inspect” panel on the right side under the “Iframe Code” section.
Step 2: Define the Model
The model is a JSON object that holds the state of your component.
For a US-based sales dashboard, your model might look like this:
{
"region": "Midwest",
"salesTarget": 50000
}Step 3: Write the React Code
Here is the full code for a custom “Sales Progress Circle” that changes color based on performance.
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@mui/material@latest/umd/material-ui.development.js"></script>
<div id="react-root"></div>
<script type="text/babel">
const { Typography, Box, CircularProgress } = MaterialUI;
const SalesComponent = () => {
// Retool specific hooks to get data and update it
const [model, setModel] = window.Retool.useModel();
const progress = (model.currentSales / model.salesTarget) * 100;
const color = progress >= 100 ? 'success' : 'primary';
return (
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', p: 2 }}>
<Typography variant="h6" gutterBottom>
{model.region} Sales Performance
</Typography>
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
<CircularProgress
variant="determinate"
value={Math.min(progress, 100)}
color={color}
size={80}
/>
<Box
sx={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography variant="caption" component="div" color="text.secondary">
{`${Math.round(progress)}%`}
</Typography>
</Box>
</Box>
<Typography variant="body2" sx={{ mt: 2 }}>
Target: ${model.salesTarget?.toLocaleString('en-US')}
</Typography>
</Box>
);
};
const ConnectedComponent = window.Retool.connectReactComponent(SalesComponent);
const root = ReactDOM.createRoot(document.getElementById('react-root'));
root.render(<ConnectedComponent />);
</script>You can refer to the screenshot below to see the output.

Method 2: Develop Locally with Create React App
For more complex components that require external libraries or many files, I prefer developing locally.
This allows me to use VS Code and modern build tools before deploying the code to Retool.
Step 1: Create a React Project
Open your terminal and create a new project. I usually name mine after the specific business function.
npx create-react-app retool-us-tax-calculator
cd retool-us-tax-calculator
npm install @mui/material @emotion/react @emotion/styledStep 2: Implement the Retool Bridge
You need to ensure your local app can communicate with the Retool parent window.
In my experience, creating a helper hook makes the code much cleaner and easier to maintain.
Step 3: Full Code Example (Tax Calculator Component)
Here is a complete example of a component that calculates the estimated US Federal Tax based on Retool inputs.
App.js
import React, { useEffect, useState } from 'react';
import { Card, CardContent, Typography, TextField, Grid } from '@mui/material';
function App() {
const [model, setModel] = useState({ income: 0, filingStatus: 'Single' });
// Sync with Retool
useEffect(() => {
const handler = (event) => {
if (event.data?.type === 'MODEL_UPDATED') {
setModel(event.data.model);
}
};
window.addEventListener('message', handler);
return () => window.removeEventListener('message', handler);
}, []);
const calculateTax = (income) => {
// Simplified 2024 Federal Tax Logic for example
if (income <= 11600) return income * 0.10;
if (income <= 47150) return 1160 + (income - 11600) * 0.12;
return 5426 + (income - 47150) * 0.22;
};
const estimatedTax = calculateTax(model.income || 0);
const handleIncomeChange = (e) => {
const newIncome = parseFloat(e.target.value) || 0;
// Update local state
setModel(prev => ({ ...prev, income: newIncome }));
// Update Retool Model
window.parent.postMessage({
type: 'UPDATE_MODEL',
model: { income: newIncome }
}, '*');
};
return (
<Card sx={{ maxWidth: 400, m: 'auto', boxShadow: 3 }}>
<CardContent>
<Typography variant="h5" color="primary" gutterBottom>
Internal Tax Estimator
</Typography>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
fullWidth
label="Annual Gross Income (USD)"
type="number"
value={model.income}
onChange={handleIncomeChange}
variant="outlined"
/>
</Grid>
<Grid item xs={12}>
<Typography variant="h6">
Est. Federal Tax:
<span style={{ color: '#d32f2f' }}>
${estimatedTax.toLocaleString('en-US', { minimumFractionDigits: 2 })}
</span>
</Typography>
</Grid>
</Grid>
</CardContent>
</Card>
);
}
export default App;You can refer to the screenshot below to see the output.

How to Trigger Retool Actions from Your Component
A common mistake I see developers make is building a component that looks great but doesn’t “talk” back to the Retool queries.
You can trigger a Retool query (like a SQL update) by using window.Retool.triggerQuery(‘queryName’).
I often use this for “Save” buttons within my custom React forms to ensure the database updates immediately.
Tips for Ranking High with Custom Components
When I build these for production, I always keep a few performance tips in mind.
First, keep your external dependencies minimal. Loading huge libraries like Three.js inside an iframe can slow down your Retool app.
Second, always use the window.Retool.subscribe method to ensure your React state stays in sync with Retool’s global state.
Handle CSS Scoping Issues
Since your component lives in an iframe, you don’t have to worry about your CSS leaking out into the rest of Retool.
However, make sure you set the iframe height to “Hug Contents” in the Retool inspector if your component grows dynamically.
I have spent hours debugging “hidden” buttons, only to realize the iframe container was just too short.
Conclusion
Building a custom React component for Retool is a game-changer for internal tool development.
It allows you to provide a bespoke user experience while still leveraging the speed of a low-code platform.
Whether you use the built-in editor for quick UI tweaks or a local development workflow for complex logic, the flexibility is unmatched.
I hope this tutorial helps you build better, more efficient tools for your team.
You may also like to read:
- React Context in Functional Components
- How to Refresh or Reload a Component in React
- How to Convert React Component to Web Component
- Build React Code Snippet Component

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.