Getting Started
Create your first dashboard
Learn how to rebuild a light version of the demo dashboard as shown on the website in less than one hour.
Hint: Before you start with this section, make sure you have followed the installation guideline. Please note, that this demo relies on the Next.js app from the installation.
1. Load a page shell
Our demo dashboard is based on one of our page shell blocks. They are used to wrap visualizations and metrics into a visually compelling interface without taking care of layout or responsiveness considerations.
For simplicity, we neglect the integration of the datepicker and input elements (dropdown, multiselect).
In our case, we will go for the page shell with tabs
and render the initial
template code to get a feeling of the appearance. We will name our Tabs
Overview
and Detail
.
Dashboard
Lorem ipsum dolor sit amet, consetetur.
import { useState } from "react"; import { Card, Grid, Tab, TabList, Text, Title } from "@tremor/react"; export default function KpiCardGrid() { const [selectedView, setSelectedView] = useState("1"); return ( <main className="bg-slate-50 p-6 sm:p-10"> <Title>Dashboard</Title> <Text>Lorem ipsum dolor sit amet, consetetur sadipscing elitr.</Text> <TabList defaultValue="1" onValueChange={(value) => setSelectedView(value)} className="mt-6" > <Tab value="1" text="Overview" /> <Tab value="2" text="Detail" /> </TabList> {selectedView === "1" ? ( <> <Grid numColsLg={3} className="mt-6 gap-6"> <Card> {/* Placeholder to set height */} <div className="h-28" /> </Card> <Card> {/* Placeholder to set height */} <div className="h-28" /> </Card> <Card> {/* Placeholder to set height */} <div className="h-28" /> </Card> </Grid> <div className="mt-6"> <Card> <div className="h-80" /> </Card> </div> </> ) : ( <Card className="mt-6"> <div className="h-96" /> </Card> )} </main> ); }
2. Add KPI cards
For the overview page, we built simple KPI cards using the Metric
, Badge
and
ProgressBar
components. Below is the composition of one KPI card.
Sales
$ 12,699
13.2%
68% ($ 149,940)
$ 220,500
import { BadgeDelta, Card, Flex, Metric, ProgressBar, Text, } from "@tremor/react"; // Single KPI card in the demo dashboard with sample inputs export default function KpiCard() { return ( <Card className="max-w-lg"> <Flex alignItems="start"> <div> <Text>Sales</Text> <Metric>$ 12,699</Metric> </div> <BadgeDelta deltaType="moderateIncrease">13.2%</BadgeDelta> </Flex> <Flex className="mt-4"> <Text className="truncate">68% ($ 149,940)</Text> <Text> $ 220,500 </Text> </Flex> <ProgressBar percentageValue={15.9} className="mt-2" /> </Card> ); }
In the case of several KPI cards as in our page shell, we use a data array and Map() method to only declare the KPI card composition once.
Hint: If the input in the KPI cards is relatively long, it is recommended to apply truncate in the corresponding components to show a potential overflow more aesthetically. If you are reading this page in desktop view, try shrinking the browser window and see how the truncate points appear at a small screen size.
Sales
$ 12,699
13.2%
15.9% ($ 12,699)
$ 80,000
Profit
$ 45,564
23.9%
36.5% ($ 45,564)
$ 125,000
Customers
1,072
10.1%
53.6% (1,072)
2,000
import { BadgeDelta, Card, Grid, DeltaType, Flex, Metric, ProgressBar, Text, } from "@tremor/react"; type Kpi = { title: string; metric: string; progress: number; target: string; delta: string; deltaType: DeltaType; }; const kpiData: Kpi[] = [ { title: "Sales", metric: "$ 12,699", progress: 15.9, target: "$ 80,000", delta: "13.2%", deltaType: "moderateIncrease", }, { title: "Profit", metric: "$ 45,564", progress: 36.5, target: "$ 125,000", delta: "23.9%", deltaType: "increase", }, { title: "Customers", metric: "1,072", progress: 53.6, target: "2,000", delta: "10.1%", deltaType: "moderateDecrease", }, ]; export default function KpiCardGrid() { return ( <Grid numColsLg={3} className="mt-6 gap-6"> {kpiData.map((item) => ( <Card key={item.title}> <Flex alignItems="start"> <div className="truncate"> <Text>{item.title}</Text> <Metric className="truncate">{item.metric}</Metric> </div> <BadgeDelta deltaType={item.deltaType}>{item.delta}</BadgeDelta> </Flex> <Flex className="mt-4 space-x-2"> <Text className="truncate">{`${item.progress}% (${item.metric})`}</Text> <Text>{item.target}</Text> </Flex> <ProgressBar percentageValue={item.progress} className="mt-2" /> </Card> ))} </Grid> ); }
3. Add chart
For the last section of the overview page, we add an AreaChart
component with
a ToggleButton
to hop between different data domains in the chart.
Hint: For the mobile view, we hide the y-axis to provide more space for the main content of the chart. This is why we introduce an additional code snippet for the chart but that is hidden when the page is called on a screen size larger than the mobile view.
Performance History
Daily increase or decrease per domain
import { useState } from "react"; import { AreaChart, Card, Flex, Icon, Text, Title, Toggle, ToggleItem, } from "@tremor/react"; import { InformationCircleIcon } from "@heroicons/react/outline"; export const performance = [ { date: "2021-01-01", Sales: 900.73, Profit: 173, Customers: 73, }, { date: "2021-01-02", Sales: 1000.74, Profit: 174.6, Customers: 74, }, // ... { date: "2021-03-13", Sales: 882, Profit: 682, Customers: 682, }, ]; // Basic formatters for the chart values const dollarFormatter = (value: number) => `$ ${Intl.NumberFormat("us").format(value).toString()}`; const numberFormatter = (value: number) => `${Intl.NumberFormat("us").format(value).toString()}`; export default function ChartView() { const [selectedKpi, setSelectedKpi] = useState("Sales"); // map formatters by selectedKpi const formatters: { [key: string]: any } = { Sales: dollarFormatter, Profit: dollarFormatter, Customers: numberFormatter, }; return ( <Card> <div className="md:flex justify-between"> <div> <Flex justifyContent="start" className="space-x-0.5" alignItems="center" > <Title> Performance History </Title> <Icon icon={InformationCircleIcon} variant="simple" tooltip="Shows day-over-day (%) changes of past performance" /> </Flex> <Text> Daily increase or decrease per domain </Text> </div> <div className="mt-6 md:mt-0"> <Toggle color="zinc" defaultValue={selectedKpi} onValueChange={(value) => setSelectedKpi(value)} > <ToggleItem value="Sales" text="Sales" /> <ToggleItem value="Profit" text="Profit" /> <ToggleItem value="Customers" text="Customers" /> </Toggle> </div> </div> <AreaChart data={performance} index="date" categories={[selectedKpi]} colors={["blue"]} showLegend={false} valueFormatter={formatters[selectedKpi]} yAxisWidth={56} className="h-96 mt-8" /> </Card> ); }
4. Add a table to the second page
For the second page, we have one big container in which we will use a table showing detailed information about the behavior of the sales performance.
Name | Leads | Sales ($) | Quota ($) | Variance | Region | Status |
---|---|---|---|---|---|---|
Peter Doe | 45 | 1,000,000 | 1,200,000 | low | Region A | overperforming |
Lena Whitehouse | 35 | 900,000 | 1,000,000 | low | Region B | average |
Phil Less | 52 | 930,000 | 1,000,000 | medium | Region C | underperforming |
John Camper | 22 | 390,000 | 250,000 | low | Region A | overperforming |
Max Balmoore | 49 | 860,000 | 750,000 | low | Region B | overperforming |
Peter Moore | 82 | 1,460,000 | 1,500,000 | low | Region A | average |
Joe Sachs | 49 | 1,230,000 | 1,800,000 | medium | Region B | underperforming |
import { useState } from 'react'; import { BadgeDelta, Card, DeltaType, Dropdown, DropdownItem, MultiSelectBox, MultiSelectBoxItem, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, } from '@tremor/react'; export type SalesPerson = { name: string; leads: number; sales: string; quota: string; variance: string; region: string; status: string; deltaType: DeltaType; }; export const salesPeople: SalesPerson[] = [ { name: 'Peter Doe', leads: 45, sales: '1,000,000', quota: '1,200,000', variance: 'low', region: 'Region A', status: 'overperforming', deltaType: 'moderateIncrease', }, { name: 'Lena Whitehouse', leads: 35, sales: '900,000', quota: '1,000,000', variance: 'low', region: 'Region B', status: 'average', deltaType: 'unchanged', }, { name: 'Phil Less', leads: 52, sales: '930,000', quota: '1,000,000', variance: 'medium', region: 'Region C', status: 'underperforming', deltaType: 'moderateDecrease', }, { name: 'John Camper', leads: 22, sales: '390,000', quota: '250,000', variance: 'low', region: 'Region A', status: 'overperforming', deltaType: 'increase', }, { name: 'Max Balmoore', leads: 49, sales: '860,000', quota: '750,000', variance: 'low', region: 'Region B', status: 'overperforming', deltaType: 'increase', }, { name: 'Peter Moore', leads: 82, sales: '1,460,000', quota: '1,500,000', variance: 'low', region: 'Region A', status: 'average', deltaType: 'unchanged', }, { name: 'Joe Sachs', leads: 49, sales: '1,230,000', quota: '1,800,000', variance: 'medium', region: 'Region B', status: 'underperforming', deltaType: 'moderateDecrease', }, ]; export default function TableView() { const [selectedStatus, setSelectedStatus] = useState('all'); const [selectedNames, setSelectedNames] = useState<string[]>([]); const isSalesPersonSelected = (salesPerson: SalesPerson) => (salesPerson.status === selectedStatus || selectedStatus === 'all') && (selectedNames.includes(salesPerson.name) || selectedNames.length === 0); return ( <Card> <div className="sm:mt-6 hidden sm:flex sm:start sm:space-x-2"> <MultiSelectBox onValueChange={ (value) => setSelectedNames(value) } placeholder="Select Salespeople" className="max-w-xs" > { salesPeople.map((item) => ( <MultiSelectBoxItem key={ item.name } value={ item.name } text={ item.name } /> )) } </MultiSelectBox> <Dropdown className="max-w-xs" defaultValue="all" onValueChange={ (value) => setSelectedStatus(value) } > <DropdownItem value="all" text="All Performances" /> <DropdownItem value="overperforming" text="Overperforming" /> <DropdownItem value="average" text="Average" /> <DropdownItem value="underperforming" text="Underperforming" /> </Dropdown> </div> <div className="mt-6 sm:hidden space-y-2 sm:space-y-0"> <MultiSelectBox onValueChange={ (value) => setSelectedNames(value) } placeholder="Select Salespeople" className="max-w-full" > { salesPeople.map((item) => ( <MultiSelectBoxItem key={ item.name } value={ item.name } text={ item.name } /> )) } </MultiSelectBox> <Dropdown className="max-w-full" defaultValue="all" onValueChange={ (value) => setSelectedStatus(value) } > <DropdownItem value="all" text="All Performances" /> <DropdownItem value="overperforming" text="Overperforming" /> <DropdownItem value="average" text="Average" /> <DropdownItem value="underperforming" text="Underperforming" /> </Dropdown> </div> <Table className="mt-6"> <TableHead> <TableRow> <TableHeaderCell>Name</TableHeaderCell> <TableHeaderCell className="text-right">Leads</TableHeaderCell> <TableHeaderCell className="text-right"> Sales ($) </TableHeaderCell> <TableHeaderCell className="text-right"> Quota ($) </TableHeaderCell> <TableHeaderCell className="text-right"> Variance </TableHeaderCell> <TableHeaderCell className="text-right">Region</TableHeaderCell> <TableHeaderCell className="text-right">Status</TableHeaderCell> </TableRow> </TableHead> <TableBody> { salesPeople .filter((item) => isSalesPersonSelected(item)) .map((item) => ( <TableRow key={ item.name }> <TableCell>{ item.name }</TableCell> <TableCell className="text-right">{ item.leads }</TableCell> <TableCell className="text-right">{ item.sales }</TableCell> <TableCell className="text-right">{ item.quota }</TableCell> <TableCell className="text-right"> { item.variance } </TableCell> <TableCell className="text-right">{ item.region }</TableCell> <TableCell className="text-right"> <BadgeDelta deltaType={ item.deltaType } size="xs"> { item.status } </BadgeDelta> </TableCell> </TableRow> )) } </TableBody> </Table> </Card> ); }
That's it! Your first dashboard is ready to be shipped. If you have encountered any issues, please check out our GitHub site and raise an issue if no answer can be found.