Template · React · HTML
Rental portfolio
A property dashboard in Tiles: portfolio figures, an area chart of income against payouts, a rent-collection dial and recent transfers.
Preview
React
Install the template with shadcn. It lands in your project as ordinary components that you edit yourself.
npx shadcn@latest add https://mikkelmanniche.dk/lab/r/rental-portfolio.jsonnpx shadcn@latest add @manniche/rental-portfoliorental-portfolio.tsx
// A rental portfolio screen: portfolio figures, income vs payout, rent collection and recent transfers.
import { cn } from '@/lib/utils'
import { AreaChart, type AreaPoint } from '@/registry/manniche/area-chart/area-chart'
import { BigNumber } from '@/registry/manniche/chart-kit/chart-kit'
import type { ValueFormat } from '@/registry/manniche/chart-kit/chart-utils'
import { DataTile, TileFact } from '@/registry/manniche/data-tile/data-tile'
import { Dial, type DialZone } from '@/registry/manniche/dial/dial'
import { TransactionList, type Transaction } from '@/registry/manniche/transaction-list/transaction-list'
export type RentalPortfolioData = {
figures: { id: string; title: string; value: number; format?: ValueFormat }[]
income: { title: string; points: AreaPoint[]; format?: ValueFormat }
collection: { title: string; value: number; zones: DialZone[]; caption: string }
transfers: { title: string; items: Transaction[]; today?: string; format?: ValueFormat }
}
export type RentalPortfolioLabels = { note?: string }
export type RentalPortfolioProps = { data: RentalPortfolioData; labels?: RentalPortfolioLabels; className?: string }
export function RentalPortfolio({ data, labels, className }: RentalPortfolioProps) {
const note = labels?.note ?? 'Example data.'
return (
<div className={cn('@container w-full rounded-2xl bg-background p-3 text-foreground', className)}>
<div className="grid grid-cols-1 gap-3 @lg:grid-cols-3 @3xl:grid-cols-6">
{data.figures.map((f, i) => (
<div key={f.id} className="@3xl:col-span-2">
<DataTile className="h-full" title={f.title} inverted={i === 0} density="compact">
<TileFact>
<BigNumber value={f.value} format={f.format} size="lg" />
</TileFact>
</DataTile>
</div>
))}
<div className="@lg:col-span-3 @3xl:col-span-4">
<DataTile className="h-full" title={data.income.title} footer={note}>
<AreaChart data={data.income.points} label={data.income.title} format={data.income.format} compare />
</DataTile>
</div>
<div className="@lg:col-span-3 @3xl:col-span-2">
<DataTile className="h-full" title={data.collection.title} footer={note}>
<Dial value={data.collection.value} min={0} max={100} zones={data.collection.zones} label={data.collection.title} caption={data.collection.caption} format={{ suffix: '%' }} />
</DataTile>
</div>
<div className="@lg:col-span-3 @3xl:col-span-6">
<DataTile className="h-full" title={data.transfers.title} footer={note}>
<TransactionList data={data.transfers.items} label={data.transfers.title} today={data.transfers.today} format={data.transfers.format} />
</DataTile>
</div>
</div>
</div>
)
}rental-portfolio-demo.tsx
import { RentalPortfolio, type RentalPortfolioData } from '@/components/rental-portfolio'
// Example data only. All names and figures are invented.
const money = { currency: 'EUR', decimals: 0 }
const data: RentalPortfolioData = {
figures: [
{ id: 'v', title: 'Portfolio value', value: 1240000, format: money },
{ id: 'u', title: 'Units', value: 14 },
{ id: 'r', title: 'Monthly rent', value: 9800, format: money },
],
income: {
title: 'Income vs payout',
format: money,
points: ['May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct'].map((label, i) => ({ label, value: [8800, 9100, 9300, 9500, 9700, 9800][i], previous: [7600, 7900, 8100, 8300, 8500, 8700][i] })),
},
collection: {
title: 'Rent collected',
value: 92,
caption: 'of rent due this month',
zones: [
{ label: 'Late', to: 70 },
{ label: 'Mostly in', to: 90 },
{ label: 'Collected', to: 100 },
],
},
transfers: {
title: 'Recent transfers',
today: '2026-10-06',
format: money,
items: [
{ id: 't1', name: 'Example tenant, unit 3', amount: 850, date: '2026-10-05', category: 'Rent' },
{ id: 't2', name: 'Sample Repairs', amount: -240, date: '2026-10-04', category: 'Repair' },
{ id: 't3', name: 'Owner payout', amount: -3000, date: '2026-10-01', category: 'Payout' },
],
},
}
export default function RentalPortfolioDemo() {
return <RentalPortfolio data={data} />
}HTML
The whole page as one file with all CSS built in and no JavaScript. Toggles, tabs and filters work with HTML and CSS alone.
The colour in your own app
If you already have shadcn set up, these variables give the same colour. Light in :root, dark in .dark.
:root {
--primary: oklch(0.52 0.2 262);
--primary-foreground: oklch(0.99 0 0);
--accent: oklch(0.95 0.034 262);
--accent-foreground: oklch(0.36 0.140 262);
--ring: oklch(0.52 0.2 262);
}
.dark {
--primary: oklch(0.74 0.150 262);
--primary-foreground: oklch(0.18 0.024 262);
--accent: oklch(0.29 0.050 262);
--accent-foreground: oklch(0.9 0.050 262);
--ring: oklch(0.74 0.150 262);
}Install
npx shadcn@latest add https://mikkelmanniche.dk/lab/r/rental-portfolio.json