Mikkel Manniche
Dansk English Deutsch

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.json
npx shadcn@latest add @manniche/rental-portfolio
rental-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.

Open in a new tab

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

Dependencies

    Files

    • rental-portfolio.tsx
    • rental-portfolio.html

    Manniche Lab · open source

    Free tools for AI agents and web development.