Skip to content
Nana UI

Rental

Unit Map

A building as an interactive explorer — each floor a row of unit cards with the unit's number, its state, a small photograph and the faces of the people who live there. Hover for rent and lease, select for the side panel.

View .md Download

Not a floor-plan diagram: an information system with a human layer. Thin borders, strong numbers and restrained colour carry the structure; the photographs and avatars say which home is which and who lives there.

Hover a unit for its rent and lease. Select one for the side panel.

┌──────────────────────────┐
│ thumbnail │ 16:9, lazy-loaded, one size on every card
├──────────────────────────┤
│ A-304 ▣ Occupied │ the number, and the state in words with an icon
│ 2 Bed · 2 Bath │ rooms, or the kind of unit
│ (JK)(MK)+1 John Kamau │ stacked avatars, then the tenant or the balance
└──────────────────────────┘
  • Avatars show who lives there without opening the unit. Up to three faces overlap; past that the last becomes a count, +2. Without a photograph a tenant gets their initials. An empty unit shows a dashed seat.
  • Thumbnails are small on purpose: the larger picture is in the hover card and the panel. Once any unit has an image, every card makes room for one so a row keeps one height; no-images drops them all.
  • States never rely on colour alone — each has its own label and icon.
ValueReads asColour
vacant or availableAvailableNeutral, with a dashed seat
occupiedOccupiedThe brand colour
reservedReservedAmber
maintenanceMaintenanceOrange
arrears or overdueOverdueRed
noticeNotice givenPurple

These come from the unit set of Status, so a <nana-status set="unit"> elsewhere on the page uses the same words and colours. A unit on notice or overdue still counts as occupied: it has a tenant.

Hovering a card lifts it, brings the thumbnail slightly closer and spreads the avatars; after a moment a card opens beside it with the larger photograph, the kind of unit, every tenant, the rent and the lease. Keyboard focus opens the same card.

Selecting a unit fires nana-unit-select. With panel, it also opens a side panel instead of sending the reader away: the photograph, the tenants with their roles, rent per month, the lease, any balance, and two actions that fire nana-unit-action with view or lease. Put your own buttons in the panel-actions slot to replace them. Escape or the close button fires nana-panel-close.

explorer turns the map into a property explorer: a rail with the building’s counts by state — each one a filter — and its floors to jump between, a search across units and tenants, and the panel on selection. On a narrow screen the rail folds away and the panel drops under the plan.

detail="balance" puts each unit’s balance beside the avatars — owed in red, credit, or paid up. With a state picked in the legend, the rest fade back.

FieldMeaning
idYour key for the unit; comes back in every event
labelThe unit’s number, as it is written on the door
floor0 is the ground, negatives are basements
statusA value from the table above
tenantsEveryone on the lease, as { name, avatar?, role? }; the first is the primary tenant
tenant, avatarA shorthand for a single tenant
imageA photograph of the unit
bedrooms, bathroomsShown as “2 Bed · 2 Bath”; 0 bedrooms is a studio
kindWhat the unit is when rooms do not say it — “Shop”, “Office”
rentMonthly rent, in currency
leaseStart, leaseEndISO dates
balancePositive is owed, negative is credit
noteRead out with the unit and shown in the panel

The plan is one tab stop. Arrow keys move along a floor and up and down between floors; Home and End go to the ends of a floor, Page Up and Page Down to the top and bottom of the building. Enter or Space selects, and Escape closes the hover card, then the panel.

For a building with dozens of units on a floor, a drawing of the floor itself — each apartment a shape you can click — places a unit faster than any grid. That needs the building’s geometry, which this component does not have; pair it with your own SVG plan and drive the same selected and highlight from both.

Photographs on this page are from Pexels.

npm install @nana-tec/ui-components

Or copy the source in and own it:

npx @nana-tec/ui add unit-map
import "@nana-tec/ui-components/unit-map";
const map = document.querySelector("nana-unit-map");
map.units = await api.units(buildingId);
map.addEventListener("nana-unit-select", (e) => history.replaceState(null, "", `#${e.detail.unit.id}`));
map.addEventListener("nana-unit-action", (e) => {
if (e.detail.action === "lease") openLease(e.detail.unit.id);
});

Properties

PropertyAttributeTypeDefaultDescription
currencycurrencystring'KES'Currency the rents and balances are in
detaildetailUnitMapDetail'tenant'What each card shows beside the avatars
explorerexplorerbooleanfalseAdd the rail of counts and floors and the search, and open the panel on selection
highlighthighlightstring''A state to isolate; the other units fade back
labellabelstring''The building's name
localelocalestring''BCP 47 locale for money and dates; the reader's own when empty
noImagesno-imagesbooleanfalseLeave the thumbnails off the cards
noLegendno-legendbooleanfalseLeave out the legend
noOccupancyno-occupancybooleanfalseLeave out the occupancy bar
panelpanelbooleanfalseOpen a side panel with the selected unit's details
queryquerystring''Text to find units or tenants by; the rest fade back
selectedselectedstring''The chosen unit's id
unitsunitsStackUnit[][]Every unit: id, label, floor and a status from the unit set, with any of tenants (or one tenant and avatar), image, kind, bedrooms, bathrooms, rent, leaseStart, leaseEnd, balance, note

Events

EventDescription
nana-unit-selectA unit was chosen (detail.unit)
nana-unit-actionAn action in the panel was chosen (detail.action is view or lease, detail.unit)
nana-panel-closeThe panel was closed

Slots

SlotDescription
emptyShown when there are no units
actionsControls beside the title, such as a building switcher or an add-unit button
panel-actionsReplaces the panel's View unit and Manage lease buttons

CSS parts

PartDescription
::part(base)The container
::part(header)The title, summary, search and actions
::part(title)The building's name
::part(summary)The occupancy line
::part(search)The search field
::part(occupancy)The bar showing the share of units in each state
::part(legend)The state legend
::part(legend-item)One legend entry
::part(rail)The explorer's side rail
::part(plan)The floors
::part(floor)One floor
::part(floor-label)The floor's name
::part(unit)One unit card
::part(unit-image)The unit's thumbnail
::part(unit-label)The unit's number
::part(unit-state)The unit's state, in words and an icon
::part(unit-facts)Bedrooms and bathrooms, or the kind of unit
::part(avatars)The stacked tenant avatars
::part(avatar)One tenant's photograph or initials
::part(unit-detail)The tenant or balance beside the avatars
::part(preview)The card shown on hover
::part(panel)The side panel for the selected unit
::part(panel-close)The panel's close button

CSS custom properties

PropertyDescription
--nana-unit-map-cell-minNarrowest a unit card may get before the row wraps
--nana-unit-map-bgContainer background
--nana-unit-map-image-ratioAspect ratio of a unit's thumbnail
--nana-unit-map-panel-widthWidth of the side panel
--nana-tone-alertColour of the alert tone, used for maintenance
--nana-tone-changeColour of the change tone, used for notice given
--nana-press-scaleHow far a unit depresses on press, shared with the other pressable controls
Explore Unit Map interactively Every state, prop, and edge case — with live controls — in Storybook.
View in Storybook