Config Driven UI
Config-driven UI means generating an interface from configuration and data instead of manually hardcoding every section.
Config + Data
↓
Rendering Logic
↓
Reusable Components
↓
Dynamic UI
One-line idea: The configuration describes what to render, while reusable components decide how it appears.
Core Concepts
The Problem with Hardcoded UI
A small interface may begin with manually repeated components:
<Card />
<Card />
<Card />
As the application grows, this approach creates problems:
- repeated markup increases
- layouts become harder to manage
- changing section order requires code changes
- customization becomes difficult
- supporting multiple UI variations takes extra effort
Config-driven UI moves the repeated structure into data:
const sections = [
{ id: "one", type: "card" },
{ id: "two", type: "card" },
{ id: "three", type: "card" },
];
The frontend reads the configuration and renders the matching components.
What Is UI Configuration?
A UI configuration is a data structure that describes the sections of an interface.
const pageConfig = [
{
id: "hero",
type: "banner",
data: {
title: "Summer Collection",
description: "Explore the latest products",
},
},
{
id: "recommended",
type: "scrollableRow",
title: "Recommended for You",
data: recommendedProducts,
},
];
The configuration can control:
- component type
- content or data
- section order
- enabled or disabled sections
- component variation
- layout selection
Config and Data
Config and data have related but different jobs.
| Value | Responsibility |
|---|---|
type | Selects the component to render |
data | Supplies the content displayed by that component |
variant | Chooses a visual or behavioral variation |
enabled | Controls whether the section is shown |
| Array order | Controls the order of sections |
Example:
{
type: "scrollableRow",
variant: "movie",
enabled: true,
data: movies
}
type -> render ScrollableRow
variant -> use movie-style cards
enabled -> show the section
data -> display the movie items
Type-Based Rendering
The type field is the core of the rendering decision.
type = "card" -> Card
type = "banner" -> Banner
type = "scrollableRow" -> ScrollableRow
A simple renderer can use conditional logic:
export default function SectionRenderer({ section }) {
if (section.type === "banner") {
return <Banner data={section.data} />;
}
if (section.type === "scrollableRow") {
return (
<ScrollableRow
title={section.title}
data={section.data}
variant={section.variant}
/>
);
}
if (section.type === "card") {
return <Card data={section.data} variant={section.variant} />;
}
return null;
}
Different configuration values produce different UI without manually rewriting the page.
Independence and Flexibility
When the interface depends on configuration:
- sections can be reordered
- layouts can change
- sections can be enabled or disabled
- the same component can display different data
- one rendering system can support multiple UI variations
Fixed page structure
↓
Configurable rendering system
↓
More freedom to change the interface
The frontend becomes a reusable system rather than a collection of fixed pages.
Practical Config-Driven Patterns
Build a Page Renderer
The page renderer loops through the configuration and delegates each section to a section renderer.
export default function PageRenderer({ config }) {
return (
<main>
{config.map((section) => (
<SectionRenderer key={section.id} section={section} />
))}
</main>
);
}
const homePageConfig = [
{
id: "main-banner",
type: "banner",
data: {
title: "Discover New Arrivals",
description: "Fresh products selected for you",
},
},
{
id: "popular-products",
type: "scrollableRow",
title: "Popular Products",
variant: "product",
data: popularProducts,
},
];
export default function HomePage() {
return <PageRenderer config={homePageConfig} />;
}
Rendering flow:
HomePage passes config
↓
PageRenderer reads every section
↓
SectionRenderer checks its type
↓
Banner or ScrollableRow renders
Use a Component Registry
As the number of section types increases, a component registry keeps the mapping in one place.
export const sectionRegistry = {
banner: Banner,
card: Card,
scrollableRow: ScrollableRow,
};
import { sectionRegistry } from "./sectionRegistry";
export default function SectionRenderer({ section }) {
if (section.enabled === false) {
return null;
}
const Component = sectionRegistry[section.type];
if (!Component) {
return null;
}
return <Component {...section} />;
}
The registry connects configuration types to reusable React components.
"banner" -> Banner
"card" -> Card
"scrollableRow" -> ScrollableRow
Config-Driven Scrollable Row
A scrollable row is reusable because its content comes from configuration.
const movieRowConfig = {
id: "trending-movies",
type: "scrollableRow",
title: "Trending Movies",
variant: "movie",
data: movies,
};
export default function ScrollableRow({ title, data, variant }) {
return (
<section className="scrollable-row">
<h2>{title}</h2>
<div className="scrollable-row__items">
{data.map((item) => (
<Card key={item.id} data={item} variant={variant} />
))}
</div>
</section>
);
}
The same component can render:
- products
- movies
- recommendations
- donation cards
- dashboard content
Only the configuration and data need to change.
Multiple UI Variations
One reusable component can support multiple presentations through a variant value.
<Card variant="movie" data={movie} />
<Card variant="product" data={product} />
<Card variant="donation" data={campaign} />
The variation can also come from configuration:
const section = {
type: "scrollableRow",
variant: "donation",
data: donationCampaigns,
};
export default function Card({ data, variant }) {
return (
<article className={`card card--${variant}`}>
<img src={data.image} alt={data.title} />
<h3>{data.title}</h3>
{variant === "product" && <p>₹{data.price}</p>}
{variant === "movie" && <p>{data.rating}/10</p>}
{variant === "donation" && <p>{data.progress}% funded</p>}
</article>
);
}
Same component structure
+
Different variant and data
↓
Different UI presentation
Enable or Disable Sections
Configuration can control whether a section is visible.
const pageConfig = [
{
id: "offer-banner",
type: "banner",
enabled: false,
data: offer,
},
{
id: "products",
type: "scrollableRow",
enabled: true,
data: products,
},
];
The renderer ignores disabled sections:
if (section.enabled === false) {
return null;
}
This changes the visible page without rewriting its JSX structure.
Change Section Order
When a page is rendered from an array, the array order becomes the UI order.
const firstLayout = [bannerSection, productSection, movieSection];
const secondLayout = [movieSection, bannerSection, productSection];
Config order changes
↓
Rendered section order changes
This makes page layouts more customizable.
Data-Driven Updates
Components do not need to be manually rewritten when their content changes.
const config = {
type: "card",
data: {
title: "Old title",
},
};
When data.title changes, the same card component renders the new title.
Config changes
Data changes
↓
React receives new props
↓
The UI updates
Benefits, Trade-Offs and Common Mistakes
Benefits
- reduces manually repeated page markup
- makes section ordering flexible
- supports reusable components and layouts
- allows sections to be enabled or disabled
- supports multiple visual variations
- separates page structure from component rendering
- makes large interfaces easier to customize
Trade-offs
| Simple hardcoded UI | Config-driven UI |
|---|---|
| Direct and easy for a very small fixed page | Better for repeated or changeable layouts |
| Structure is visible directly in JSX | Structure is split between config and renderer |
| Changes require editing the page | Many changes can be made through config |
| Repetition grows across similar pages | Reusable renderers reduce duplication |
Config-driven UI is most useful when several screens share components or when content, order and variations change. A small fixed screen may not need the extra rendering layer.
Common mistakes
| Mistake | Better approach |
|---|---|
| Hardcoding every repeated section | Store repeated structure in configuration |
| Rendering all types in the page component | Use a dedicated section renderer |
| Using one component for every possible layout | Create focused reusable components and variants |
| Mixing content directly into rendering conditions | Keep content in data |
Ignoring unknown type values | Return a safe fallback or skip the section |
Repeating long if chains as types grow | Keep type-to-component mappings in a registry |
| Forgetting stable keys for configured sections | Give every section a stable id |
Summary
Config-driven UI turns reusable React components into a flexible rendering system controlled by configuration and data.
Page configuration arrives
↓
PageRenderer loops through sections
↓
SectionRenderer reads each type
↓
Registry selects a reusable component
↓
Data and variant become component props
↓
Components render the final UI