Use CSS Variables instead of React Context
How and why you should use CSS variables (custom properties) for theming instead of React context.

If you've ever used a controlled input in React, you've already experienced the power of the Control Props pattern—even if you didn't know its name.
But what if you could bring that same flexibility to your own components? That's what the Control Props pattern is all about.
The Control Props pattern lets users of your component take full control over
its state—just like how you can control the value of an <input />
with React
state. By default, your component manages its own state, but if a user provides
a "control prop" (like value
), your component defers to them and simply
"suggests" state changes via callbacks (like onChange
).
This is the same pattern you use every time you write a controlled input:
function MyCapitalizedInput() { const [capitalizedValue, setCapitalizedValue] = useState('') return ( <input value={capitalizedValue} onChange={(e) => setCapitalizedValue(e.target.value.toUpperCase())} /> )}
Here, the <input />
is "controlled" by your state. If you don't provide a
value
prop, it manages its own state. But as soon as you do, you're in charge.
Control props "inverts control" from your component to the developer using it.
For example, you can synchronize two inputs with different transformations:
function MyTwoInputs() { const [capitalizedValue, setCapitalizedValue] = useState('') const [lowerCasedValue, setLowerCasedValue] = useState('') function handleInputChange(e) { setCapitalizedValue(e.target.value.toUpperCase()) setLowerCasedValue(e.target.value.toLowerCase()) } return ( <> <input value={capitalizedValue} onChange={handleInputChange} /> <input value={lowerCasedValue} onChange={handleInputChange} /> </> )}
This isn't just a pattern for inputs. It's used in powerful libraries like downshift and Radix UI Select. These libraries let you control their state from the outside, making them incredibly flexible and easy to integrate into any app.
Here's a simple example of a controlled select using Radix UI:
import * as React from 'react'import { Select } from '@radix-ui/react-select'export default function ControlledSelect() { const [value, setValue] = React.useState('apple') return ( <Select.Root value={value} onValueChange={setValue}> <Select.Trigger> <Select.Value placeholder="Pick a fruit" /> <Select.Icon /> </Select.Trigger> <Select.Portal> <Select.Content> <Select.Item value="apple">Apple</Select.Item> <Select.Item value="orange">Orange</Select.Item> <Select.Item value="grape">Grape</Select.Item> </Select.Content> </Select.Portal> </Select.Root> )}
You control the value from outside the component, and Radix just "suggests"
changes via onValueChange
—a perfect example of the control props pattern in
action. This allows you to programatically control the value of the select,
while still allowing the user to select from the list.
See the official docs for more.
This can actually get a little complex (which is why we have an exercise on it in EpicReact.dev). The reason it's tricky is because you want to support both controlled and uncontrolled modes.
Here's a simple implementation:
function MySelect({ value, onChange }) { const [internalValue, setInternalValue] = useState(value) function handleChange(e) { setInternalValue(e.target.value) onChange?.(e) } return ( <select value={value ?? internalValue} onChange={handleChange}> <option value="apple">Apple</option> </select> )}
This allows you to use the component in both controlled and uncontrolled modes.
There's definitely more to it, but hopefully this gives you an idea of what's going on.
When should you use control props? Reach for this pattern when you want your component to be both easy to use and highly customizable, when you want to let users sync or transform state across multiple components, or when you want to empower users to manage state in the way that fits their app.
Want to master patterns like this—and many more? EpicReact.dev is packed with hands-on workshops, real-world examples, and deep dives into the patterns that make React apps scalable and maintainable. Whether you're a beginner or a seasoned pro, you'll find something to level up your React skills.
Delivered straight to your inbox.
How and why you should use CSS variables (custom properties) for theming instead of React context.
Simplify and speed up your app development using React composition
Whether you're brand new to building dynamic web applications or you've been working with React for a long time, let's contextualize the most widely used UI framework in the world: React.
Truly maintainable, flexible, simple, and reusable components require more thought than: "I need it to do this differently, so I'll accept a new prop for that". Seasoned React developers know this leads to nothing but pain and frustration for both the maintainer and user of the component.