Maps and plans
A district map for your own pins, a city map with a label, a small map for contact areas and a floor plan with room names. Drawn in the theme colors.
One of 5 components in Graphics
2 variants
Details
What Maps and plans brings along, read from the registry and the source.
- Category
- Graphics
- Area
- Visuals
- Variants
- 2
- Version
- 1.0.0 · since 1.0.0
- File
- components/ui/maps.tsx
- Brings along
- utilskit
- npm packages
- None
- Languages
- Texts come through props
- Studio
- Adjust in code
- Build with AI
- Over MCP in Claude, Cursor, Copilot and Codex
Accessibility
Read from the source of the component.
- Needs no client state. The server writes it in full, so it reads without JavaScript too.
Motion
- Brings animations of its own, calmly timed and without wobble.
Source
The file aiw add puts into your project. From then on it is yours.
Show the file, 127 lines
components/ui/maps.tsx
'use client';
import { cn, useLocale } from '@/lib/kit';
/*
* Maps and plans: a small street map with a pin, a city map, a district map
* without pins and a floor plan. Drawn in the kit tokens, so they follow the
* theme and the accent.
*/
const ROOMS = {
en: ['Living', 'Bedroom', 'Kitchen', 'Bath', 'Balcony', 'Hall'],
de: ['Wohnen', 'Schlafen', 'Küche', 'Bad', 'Balkon', 'Flur'],
};
/** A small street map with a pin, for contact areas */
export function MapSketch({ className }: { className?: string }) {
return (
<div className={cn('relative overflow-hidden bg-surface-2', className)} aria-hidden data-clip-ok>
<svg viewBox="0 0 400 240" preserveAspectRatio="xMidYMid slice" className="absolute inset-0 size-full">
<g fill="none" stroke="var(--border-strong)" strokeLinecap="round">
<path d="M-10 70 C 90 60, 160 110, 410 90" strokeWidth="10" stroke="var(--background)" />
<path d="M120 -10 C 140 80, 110 160, 150 250" strokeWidth="8" stroke="var(--background)" />
<path d="M-10 190 L 410 160" strokeWidth="6" stroke="var(--background)" />
<path d="M260 -10 L 290 250" strokeWidth="6" stroke="var(--background)" />
<path d="M-10 130 H 410 M 60 -10 V 250 M 340 -10 V 250" strokeWidth="1" />
</g>
<path d="M300 200 q 30 -30 80 -20 v 70 h -100 z" fill="color-mix(in oklch, var(--success) 18%, transparent)" />
</svg>
<div className="absolute left-[46%] top-[38%] -translate-x-1/2 -translate-y-full">
<span className="absolute left-1/2 top-full size-10 -translate-x-1/2 -translate-y-1/2 animate-kit-pulse rounded-full bg-[color-mix(in_oklab,var(--primary)_25%,transparent)]" />
<svg viewBox="0 0 24 24" className="relative size-9 drop-shadow-md" fill="var(--primary)" stroke="var(--primary-foreground)" strokeWidth="1.4">
<path d="M12 22c4.4-4.6 7-8.3 7-11.8a7 7 0 0 0-14 0C5 13.7 7.6 17.4 12 22Z" />
<circle cx="12" cy="10" r="2.6" fill="var(--primary-foreground)" stroke="none" />
</svg>
</div>
</div>
);
}
/** A quiet city map with streets, a river and a park. The pin sits in the center. */
export function StreetMap({ className, label }: { className?: string; label?: string }) {
const road = { fill: 'none', stroke: 'var(--background)', strokeLinecap: 'round' as const };
const park = { fill: 'color-mix(in oklab, var(--success) 14%, var(--surface-2))' };
return (
<div className={cn('relative overflow-hidden bg-surface-2', className)} aria-hidden data-clip-ok>
<svg viewBox="0 0 800 500" preserveAspectRatio="xMidYMid slice" className="absolute inset-0 size-full">
<rect width="800" height="500" style={{ fill: 'var(--surface-2)' }} />
<path d="M520 -10 C 600 80, 760 120, 820 90 V -10 Z" style={park} />
<rect x="60" y="330" width="140" height="110" rx="8" style={park} />
<path d="M-20 140 C 120 160, 220 260, 360 280 S 600 300, 820 420" style={{ fill: 'none', stroke: 'color-mix(in oklab, var(--primary) 22%, var(--surface-2))', strokeWidth: 34 }} />
{[60, 140, 220, 300, 380, 460].map((y) => (
<path key={`h${y}`} d={`M-10 ${y} H810`} style={{ ...road, strokeWidth: y === 220 ? 14 : 6 }} />
))}
{[80, 180, 290, 400, 520, 630, 740].map((x) => (
<path key={`v${x}`} d={`M${x} -10 V510`} style={{ ...road, strokeWidth: x === 400 ? 14 : 6 }} />
))}
<path d="M-10 480 L560 -10" style={{ ...road, strokeWidth: 10 }} />
{[[100, 76, 64, 50], [196, 76, 80, 50], [420, 236, 84, 50], [536, 236, 80, 50], [646, 316, 80, 50], [306, 316, 80, 50], [196, 396, 80, 50], [536, 396, 80, 50]].map(([x, y, w, h], i) => (
<rect key={i} x={x} y={y} width={w} height={h} rx="4" style={{ fill: 'color-mix(in oklab, var(--foreground) 5%, var(--surface-2))' }} />
))}
<circle cx="400" cy="220" r="56" style={{ fill: 'color-mix(in oklab, var(--primary) 12%, transparent)' }} />
<circle cx="400" cy="220" r="11" style={{ fill: 'var(--primary)', stroke: 'var(--background)', strokeWidth: 4 }} />
</svg>
{label && <span className="absolute left-1/2 top-[44%] -translate-x-1/2 -translate-y-[160%] whitespace-nowrap rounded-md bg-background px-2 py-1 text-[11px] font-medium text-foreground shadow-[var(--shadow-md)]">{label}</span>}
</div>
);
}
/** A quiet district map without a pin: streets, a river, parks, blocks. Pins are placed by the block. coast adds a shoreline. */
export function DistrictMap({ className, coast = false }: { className?: string; coast?: boolean }) {
const road = { fill: 'none', stroke: 'var(--background)', strokeLinecap: 'round' as const };
const park = { fill: 'color-mix(in oklab, var(--success) 15%, var(--surface-2))' };
const water = { fill: 'color-mix(in oklab, var(--info) 18%, var(--surface-2))' };
const block = { fill: 'color-mix(in oklab, var(--foreground) 5%, var(--surface-2))' };
return (
<div className={cn('relative overflow-hidden bg-surface-2', className)} aria-hidden data-clip-ok>
<svg viewBox="0 0 1000 700" preserveAspectRatio="xMidYMid slice" className="absolute inset-0 size-full">
<rect width="1000" height="700" style={{ fill: 'var(--surface-2)' }} />
{coast ? <path d="M1010 -10 V 710 H 760 C 700 600, 790 520, 720 420 S 760 200, 700 120 S 760 20, 740 -10 Z" style={water} /> : <path d="M-20 520 C 160 480, 300 560, 480 520 S 760 420, 1020 470" style={{ fill: 'none', stroke: 'color-mix(in oklab, var(--info) 22%, var(--surface-2))', strokeWidth: 40 }} />}
<rect x="110" y="110" width="170" height="120" rx="10" style={park} />
<path d="M560 560 C 620 540, 680 600, 660 660 L 520 680 Z" style={park} />
{[[310, 90, 90, 70], [420, 90, 120, 70], [310, 190, 90, 60], [420, 190, 120, 60], [560, 90, 100, 70], [560, 190, 100, 60], [110, 270, 170, 80], [310, 290, 230, 80], [560, 290, 100, 80], [110, 390, 120, 60], [250, 390, 140, 60], [420, 390, 120, 50], [560, 380, 90, 50], [110, 600, 160, 70], [300, 600, 160, 70]].map(([x, y, w, h], i) => (
<rect key={i} x={x} y={y} width={w} height={h} rx="6" style={block} />
))}
{[70, 170, 260, 370, 470, 580].map((y) => (
<path key={`h${y}`} d={`M-10 ${y} H${coast ? 720 : 1010}`} style={{ ...road, strokeWidth: y === 260 ? 16 : 7 }} />
))}
{[90, 295, 405, 550, 680, 850].map((x) => (
<path key={`v${x}`} d={`M${x} -10 V710`} style={{ ...road, strokeWidth: x === 405 ? 16 : 7, display: coast && x > 700 ? 'none' : undefined }} />
))}
<path d="M-10 690 L 700 -10" style={{ ...road, strokeWidth: 10, opacity: 0.9 }} />
<path d="M-10 260 H 1010" style={{ fill: 'none', stroke: 'color-mix(in oklab, var(--warning) 30%, var(--background))', strokeWidth: 6, strokeLinecap: 'round' }} />
</svg>
</div>
);
}
/** A floor plan in thin lines, in the theme colors: three rooms, kitchen, bath and a balcony */
export function FloorPlan({ className, labels: labelsProp }: { className?: string; labels?: string[] }) {
const locale = useLocale();
const labels = labelsProp ?? ROOMS[locale];
const wall = { fill: 'none', stroke: 'var(--foreground)', strokeWidth: 6, strokeLinejoin: 'miter' as const };
const thin = { fill: 'none', stroke: 'var(--border-strong)', strokeWidth: 2 };
const txt = { fill: 'var(--muted-foreground)', fontSize: 15, fontFamily: 'inherit' } as const;
return (
<svg viewBox="0 0 600 420" className={cn('size-full', className)} aria-hidden>
<rect x="40" y="40" width="520" height="300" style={{ fill: 'var(--background)' }} />
<rect x="40" y="340" width="260" height="50" style={{ fill: 'color-mix(in oklab, var(--primary) 6%, var(--background))', stroke: 'var(--border-strong)', strokeDasharray: '6 5', strokeWidth: 2 }} />
<path d="M40 40 H560 V340 H40 Z" style={wall} />
<path d="M300 40 V150 M300 190 V340 M300 230 H420 M460 230 H560 M420 40 V120 M420 160 V230 M40 220 H120 M160 220 H300" style={wall} />
<path d="M300 150 a40 40 0 0 1 40 40 M120 220 a40 40 0 0 0 40 -40 M420 120 a40 40 0 0 1 40 40" style={thin} />
<path d="M90 340 h120" style={{ ...wall, stroke: 'var(--background)', strokeWidth: 8 }} />
<path d="M90 340 h120" style={{ fill: 'none', stroke: 'var(--border-strong)', strokeWidth: 2 }} />
<rect x="470" y="60" width="70" height="40" rx="4" style={thin} />
<rect x="440" y="250" width="100" height="60" rx="16" style={thin} />
<rect x="60" y="60" width="90" height="120" rx="6" style={thin} />
<path d="M320 60 h80 v40 h-80 Z M320 290 h60" style={thin} />
<text x="170" y="140" textAnchor="middle" style={txt}>{labels[0]}</text>
<text x="170" y="290" textAnchor="middle" style={txt}>{labels[1]}</text>
<text x="360" y="140" textAnchor="middle" style={txt}>{labels[2]}</text>
<text x="490" y="210" textAnchor="middle" style={txt}>{labels[3]}</text>
<text x="170" y="371" textAnchor="middle" style={txt}>{labels[4]}</text>
<text x="360" y="270" textAnchor="middle" style={txt}>{labels[5]}</text>
</svg>
);
}Frequent questions
How do I install Maps and plans?
With aiw add @ai-website/maps. The command puts components/ui/maps.tsx into your project, the code is yours from then on. It needs no further npm packages.
Which variants does Maps and plans have?
2 variants, City maps and Floor plan.
Can an AI assistant build with Maps and plans?
Yes. Through the MCP server of the aiw CLI, Claude, Cursor, GitHub Copilot, Windsurf, Codex and Gemini find Maps and plans with aiw_components and install it with aiw_add @ai-website/maps.