On Rekolt, an agronomic platform I am building, six profiles log into the same product: producer, buyer, distributor, seller, carrier and advisor.

They have almost nothing in common. The producer follows harvests, the carrier follows deliveries, the advisor follows the farms they support. Showing them all the same home screen would make no sense. But building six completely separate dashboards means six times more code to maintain, and six interfaces that slowly drift apart.

Here are the principles that get you out of that dilemma.

Separate structure from content

A dashboard, whatever the profession, is made of the same building blocks:

  • key indicators (KPIs);
  • charts, including forecasts;
  • quick actions specific to the profession.

What changes from one profile to another is not the shape of the blocks, it is which ones appear and with which data. So each dashboard can be described as a configuration and rendered with shared components.

Describe a profession by its configuration

Here is the principle, in simplified form:

dashboards.config.ts
ts
type Role = "producer" | "buyer" | "carrier";
 
type DashboardConfig = {
    kpis: string[];
    charts: string[];
    quickActions: string[];
};
 
export const dashboards: Record<Role, DashboardConfig> = {
    producer: {
        kpis: ["harvestVolume", "activeListings", "monthlyRevenue"],
        charts: ["yieldForecast"],
        quickActions: ["publishListing", "declareHarvest"],
    },
    buyer: {
        kpis: ["openOrders", "spendThisMonth", "savedSuppliers"],
        charts: ["priceTrends"],
        quickActions: ["newOrder", "browseMarketplace"],
    },
    carrier: {
        kpis: ["pendingDeliveries", "distanceThisWeek", "rating"],
        charts: ["deliveryLoad"],
        quickActions: ["acceptDelivery", "updateStatus"],
    },
};

Thanks to the Record<Role, DashboardConfig> type, TypeScript will not let you forget a profession.

One component for every rendering

The dashboard becomes a generic component that reads the configuration of the signed-in profile:

components/Dashboard.tsx
tsx
export function Dashboard({ role }: { role: Role }) {
    const config = dashboards[role];
 
    return (
        <div className="space-y-6">
            <div className="grid grid-cols-2 lg:grid-cols-3 gap-4">
                {config.kpis.map((kpi) => (
                    <KpiCard key={kpi} metric={kpi} />
                ))}
            </div>
 
            {config.charts.map((chart) => (
                <ChartCard key={chart} chart={chart} />
            ))}
 
            <QuickActions actions={config.quickActions} />
        </div>
    );
}

Adding a seventh profession means adding a configuration entry. Fixing a display bug in KpiCard fixes it for everyone.

Knowing when to break out of the frame

Configuration has its limits. Some screens are too specific to fit a common mould: on Rekolt, the AI agronomy assistant, with its local weather, recommendations and visual plant disease diagnosis, is a module in its own right.

Information density

Professional users check their dashboard to make quick decisions. They want a lot of information at a glance, not an airy screen with three numbers.

To keep that density readable, consistency is essential:

  • the same sizes for icons, secondary text and spacing on every screen;
  • the same navigation, including inside immersive modules, so users never feel lost;
  • a strict visual hierarchy: the important number large, its context small.

When every card follows the same rules, the eye learns to read the interface once, then scans it very quickly.

Key takeaways

  • Identify the building blocks shared by every profession.
  • Describe each profession with a typed configuration, rendered by shared components.
  • Keep dedicated components for truly unique screens.
  • Standardise sizes and spacing so a dense interface stays readable.