---
name: location
description: Use this skill to design functional and beautiful screens featuring interactive maps for location-based applications like store locators, delivery tracking, property listings, and geographic visualizations.
---

# Location Screen Design Skill

You are designing beautiful, functional screens that showcase location-based data with interactive maps. This skill helps you create complete screen experiences that combine maps with supporting UI elements for real-world location use cases.

## ⚠️ CRITICAL REQUIREMENTS - READ FIRST

### 1. Marker Pattern (MANDATORY)

**ALL markers MUST use this exact pattern:**

```tsx
<MarkerContent>
  <div className="size-8 rounded-full border-2 border-white bg-[color] text-white shadow-lg flex items-center justify-center">
    <IconName className="size-4" />
  </div>
</MarkerContent>
```

**Required classes:** `rounded-full`, `border-2 border-white`, `shadow-lg`, `text-white`, `flex items-center justify-center`

### 2. Map Container (REQUIRED)

**Maps MUST have explicit height and should use as much space as possible:**

```tsx
// Preferred: Use viewport-relative heights for full-screen map experiences
<div className="h-[calc(100vh-12rem)]">  {/* Full height minus header/nav */}
  <Map center={...} zoom={...}>

// Alternative: Large fixed heights
<div className="h-[600px] lg:h-[800px]">  {/* Responsive, larger on desktop */}
  <Map center={...} zoom={...}>

// Minimum (only for small preview maps)
<div className="h-[400px]">
  <Map center={...} zoom={...}>
```

**The map is the centerpiece** - make it prominent, large, and easy to interact with.

### 3. Coordinate Format

Always use `[longitude, latitude]` NOT `[lat, lng]`:

```tsx
<Map center={[-74.006, 40.7128]} zoom={12} />  {/* [lng, lat] */}
```

### 4. Theme Awareness

- Map automatically switches between light/dark styles based on app theme
- **NEVER** add satellite/street view toggle buttons
- The map style is theme-based only

## Core Design Principles

**Maps are the centerpiece of location screens.** Always prioritize giving the map as much screen real estate as possible:

- Use viewport-relative heights: `h-[calc(100vh-12rem)]` for full-screen experiences
- In split layouts, map gets `flex-1` (flexible space) while sidebars stay fixed width
- Responsive sizing: Larger maps on desktop, adequate size on mobile (min `h-[400px]`)
- Lists and supporting content should be compact and scrollable, not competing with the map

**Minimize card usage for location screens:**

- **NEVER** wrap maps in cards - maps should fill their container directly
- **Use cards sparingly:** Only for grouped supplementary info (e.g., status banners, contact info sidebars)
- **For comprehensive card guidance:** Call `read_skill` with `card-design` to understand when cards are appropriate
- **Location screen specific:** Cards compete with map for space - prioritize the map

## Common Location Screen Patterns

### 1. Store/Location Finder

**Purpose:** Help users discover and interact with physical locations

**Key Elements:**

- Full-height map with location markers
- Sidebar with searchable location list
- Active location highlighting
- "Get Directions" actions

**Layout:**

```tsx
<ScreenWrapper className="space-y-4">
  <ScreenTitle title="Store Locations" subtitle="Find a location near you" />

  <div className="flex flex-col lg:flex-row gap-6 h-[calc(100vh-12rem)]">
    {/* Location List - Left side */}
    <div className="w-full lg:w-96 flex flex-col gap-4 lg:overflow-y-auto">
      <Input type="search" placeholder="Search locations..." />

      <div className="space-y-3">
        {locations.map((location) => (
          <Card
            key={location.id}
            onClick={() => setActiveLocation(location)}
            className={cn(
              "cursor-pointer transition-all",
              activeLocation?.id === location.id && "ring-2 ring-accent",
            )}
          >
            <CardContent className="p-4">
              <h3 className="font-semibold">{location.name}</h3>
              <p className="text-sm text-subtle">{location.address}</p>
              <Badge>{location.distance} mi</Badge>
            </CardContent>
          </Card>
        ))}
      </div>
    </div>

    {/* Map - Right side */}
    <div className="flex-1 h-[400px] lg:h-auto">
      <Map center={mapCenter} zoom={12}>
        {locations.map((location) => (
          <MapMarker
            key={location.id}
            longitude={location.longitude}
            latitude={location.latitude}
            onClick={() => setActiveLocation(location)}
          >
            <MarkerContent>
              <div
                className={cn(
                  "size-8 rounded-full border-2 border-white bg-accent text-white shadow-lg flex items-center justify-center transition-transform",
                  activeLocation?.id === location.id && "scale-125",
                )}
              >
                <MapPin className="size-4" />
              </div>
            </MarkerContent>

            {activeLocation?.id === location.id && (
              <MarkerPopup closeButton onClose={() => setActiveLocation(null)}>
                <div className="space-y-2 min-w-[200px]">
                  <h3 className="font-bold">{location.name}</h3>
                  <p className="text-sm">{location.address}</p>
                  <Button size="sm">Directions</Button>
                </div>
              </MarkerPopup>
            )}
          </MapMarker>
        ))}
        <MapControls showZoom showLocate />
      </Map>
    </div>
  </div>
</ScreenWrapper>
```

### 2. Delivery/Asset Tracking

**Purpose:** Track deliveries, vehicles, or assets

**Which layout to use?**

- **Layout A (Single Delivery):** One delivery with route visualization
- **Layout B (Multi-Delivery Dashboard):** Multiple deliveries with different statuses

#### Layout A: Single Delivery with Route

```tsx
<ScreenWrapper className="space-y-4">
  <ScreenTitle title="Delivery Tracking" subtitle={`Order #${orderId}`} />

  {/* Status banner - card used for grouped info */}
  <Card>
    <CardContent className="p-6">
      <div className="flex items-center justify-between">
        <div>
          <h3 className="text-lg font-semibold">
            {vehicleName} is {distance} away
          </h3>
          <p className="text-subtle">Estimated arrival: {eta}</p>
        </div>
        <Badge variant="success">{status}</Badge>
      </div>
    </CardContent>
  </Card>

  {/* Map - no card wrapper, fills space directly */}
  <div className="h-[calc(100vh-16rem)]">
    <Map center={routeCenter} zoom={13}>
      <MapRoute coordinates={routeCoordinates} color="#3b82f6" />

      {/* Destination */}
      <MapMarker longitude={destination.lng} latitude={destination.lat}>
        <MarkerContent>
          <div className="size-10 rounded-full border-2 border-white bg-green-500 text-white shadow-lg flex items-center justify-center">
            <Home className="size-5" />
          </div>
        </MarkerContent>
      </MapMarker>

      {/* Vehicle */}
      <MapMarker longitude={vehicle.lng} latitude={vehicle.lat}>
        <MarkerContent>
          <div className="size-12 rounded-full border-2 border-white bg-blue-500 text-white shadow-lg flex items-center justify-center">
            <Truck className="size-6" />
          </div>
        </MarkerContent>
      </MapMarker>

      <MapControls showZoom showFullscreen />
    </Map>
  </div>
</ScreenWrapper>
```

#### Layout B: Multi-Delivery Dashboard

```tsx
<ScreenWrapper className="space-y-4">
  <ScreenTitle title="Deliveries" subtitle="Track all deliveries" />

  <div className="flex flex-col lg:flex-row gap-6 h-[calc(100vh-12rem)]">
    {/* Delivery List */}
    <div className="w-full lg:w-96 flex flex-col gap-4">
      <Input type="search" placeholder="Search deliveries..." />

      <div className="flex-1 overflow-y-auto space-y-3">
        {deliveries.map((delivery) => {
          const statusColors = {
            completed: "bg-gray-400",
            "in-progress": "bg-cyan-500",
            upcoming: "bg-green-500",
          };

          const locationIcons = {
            home: Home,
            office: Building,
            retail: Store,
          };

          const Icon = locationIcons[delivery.locationType] || MapPin;

          return (
            <Card
              key={delivery.id}
              onClick={() => setActive(delivery)}
              className={cn(
                "cursor-pointer transition-all",
                active?.id === delivery.id && "ring-2 ring-accent",
              )}
            >
              <CardContent className="p-4">
                <div className="flex items-start gap-3">
                  <div
                    className={cn(
                      "size-10 rounded-full border-2 border-white text-white shadow-lg flex items-center justify-center shrink-0",
                      statusColors[delivery.status],
                    )}
                  >
                    <Icon className="size-5" />
                  </div>

                  <div className="flex-1 min-w-0">
                    <h3 className="font-semibold truncate">{delivery.name}</h3>
                    <p className="text-sm text-subtle truncate">
                      {delivery.address}
                    </p>
                    <Badge size="sm">{delivery.status}</Badge>
                  </div>
                </div>
              </CardContent>
            </Card>
          );
        })}
      </div>
    </div>

    {/* Map */}
    <div className="flex-1 h-[400px] lg:h-auto">
      <Map center={mapCenter} zoom={12}>
        {deliveries.map((delivery) => {
          const statusColors = {
            completed: "bg-gray-400",
            "in-progress": "bg-cyan-500",
            upcoming: "bg-green-500",
          };

          const Icon = locationIcons[delivery.locationType] || MapPin;

          return (
            <MapMarker
              key={delivery.id}
              longitude={delivery.longitude}
              latitude={delivery.latitude}
              onClick={() => setActive(delivery)}
            >
              <MarkerContent>
                <div
                  className={cn(
                    "size-8 rounded-full border-2 border-white text-white shadow-lg flex items-center justify-center transition-transform",
                    statusColors[delivery.status],
                    active?.id === delivery.id && "scale-125",
                  )}
                >
                  <Icon className="size-4" />
                </div>
              </MarkerContent>

              {active?.id === delivery.id && (
                <MarkerPopup closeButton onClose={() => setActive(null)}>
                  <div className="space-y-2 min-w-[220px]">
                    <h3 className="font-bold">{delivery.name}</h3>
                    <p className="text-sm text-subtle">{delivery.address}</p>
                    <Badge>{delivery.status}</Badge>
                    <Button size="sm" className="w-full">
                      View Details
                    </Button>
                  </div>
                </MarkerPopup>
              )}
            </MapMarker>
          );
        })}
        <MapControls showZoom showLocate showFullscreen />
      </Map>
    </div>
  </div>
</ScreenWrapper>
```

### 3. Geographic Dashboard/Heatmap

**Purpose:** Visualize data across regions

**Key Elements:**

- Large map with data visualization
- 2-4 key metric cards maximum (only the most critical metrics - see card-design skill for metric card patterns)
- Regional filters
- Clustering for large datasets

**Layout:**

```tsx
<ScreenWrapper className="space-y-6">
  <ScreenTitle title="Regional Performance" />

  {/* Metrics - only show 2-4 critical metrics */}
  <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
    <Card>
      <CardContent className="p-4">
        <p className="text-sm text-subtle">Total Locations</p>
        <p className="text-2xl font-bold">{total}</p>
      </CardContent>
    </Card>
    <Card>
      <CardContent className="p-4">
        <p className="text-sm text-subtle">Active</p>
        <p className="text-2xl font-bold">{active}</p>
      </CardContent>
    </Card>
    {/* Only 2-4 cards total */}
  </div>

  {/* Map - no card wrapper, maximum space */}
  <div className="h-[calc(100vh-20rem)]">
    <Map center={[-98, 39]} zoom={4}>
      <MapClusterLayer
        data={geoJsonData}
        clusterRadius={50}
        pointColor="#3b82f6"
      />
      <MapControls showZoom showFullscreen />
    </Map>
  </div>
</ScreenWrapper>
```

### 4. Property/Listing Map

**Purpose:** Browse properties or items with location component

**Layout:**

```tsx
<ScreenWrapper className="space-y-4">
  <FilterToolbar>
    <FilterToolbarSearch placeholder="Search properties..." />
    <Select>
      <SelectTrigger>Price Range</SelectTrigger>
      {/* Options */}
    </Select>
    <Button onClick={toggleView}>
      {viewMode === "map" ? "List View" : "Map View"}
    </Button>
  </FilterToolbar>

  {viewMode === "map" ? (
    <div className="h-[calc(100vh-200px)]">
      <Map center={mapCenter} zoom={11}>
        <MapClusterLayer
          data={propertyGeoJson}
          clusterRadius={60}
          pointColor="#10b981"
          onPointClick={setSelected}
        />
        <MapControls showZoom showLocate showFullscreen />
      </Map>
    </div>
  ) : (
    <Grid data={properties} renderItem={PropertyCard} />
  )}
</ScreenWrapper>
```

### 5. Venue Location Detail

**Purpose:** Show detailed information about a specific location

**Layout:**

```tsx
<ScreenWrapper className="space-y-4">
  <Breadcrumb items={[...]} />

  <ScreenTitle
    variant="cover"
    coverImage={venue.image}
    title={venue.name}
    subtitle={venue.address}
    buttons={[
      <Button>Directions</Button>,
      <Button variant="outline">Share</Button>
    ]}
  />

  <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
    {/* Map - takes 2/3 of space, no card wrapper */}
    <div className="lg:col-span-2">
      <div className="h-[500px] lg:h-[600px]">
        <Map center={[venue.lng, venue.lat]} zoom={15}>
          <MapMarker longitude={venue.lng} latitude={venue.lat}>
            <MarkerContent>
              <div className="size-12 rounded-full border-2 border-white bg-red-500 text-white shadow-lg flex items-center justify-center">
                <MapPin className="size-6" />
              </div>
            </MarkerContent>
          </MapMarker>
          <MapControls showZoom showFullscreen />
        </Map>
      </div>
    </div>

    {/* Sidebar - 1/3 of space, single card for grouped contact info */}
    <Card>
      <CardContent className="p-6">
        <h3 className="font-semibold mb-4">Contact Information</h3>
        <div className="space-y-3">
          <div>
            <p className="text-sm text-subtle">Address</p>
            <p className="font-medium">{venue.address}</p>
          </div>
          <div>
            <p className="text-sm text-subtle">Phone</p>
            <p className="font-medium">{venue.phone}</p>
          </div>
          <div>
            <p className="text-sm text-subtle">Hours</p>
            <p className="font-medium">{venue.hours}</p>
          </div>
        </div>
      </CardContent>
    </Card>
  </div>
</ScreenWrapper>
```

## Data Structures

### Store/Location

```typescript
interface Location {
  id: string;
  name: string;
  address: string;
  city: string;
  state: string;
  longitude: number;
  latitude: number;
  phone: string;
  hours: string;
  status: "open" | "closed";
  distance?: number;
}
```

### Delivery (Single)

```typescript
interface DeliveryTracking {
  orderId: string;
  driverName: string;
  currentPosition: { longitude: number; latitude: number };
  destination: { address: string; longitude: number; latitude: number };
  route: [number, number][]; // [lng, lat] pairs
  status: "picked-up" | "in-transit" | "nearby" | "delivered";
  estimatedArrival: string;
}
```

### Delivery (Multi-Dashboard)

```typescript
interface Delivery {
  id: string;
  name: string;
  address: string;
  longitude: number;
  latitude: number;
  status: "completed" | "in-progress" | "upcoming";
  locationType: "home" | "office" | "retail" | "warehouse";
  estimatedTime?: string;
}
```

## Design Best Practices

### Visual Hierarchy

1. **Primary:** Map is the hero element - use maximum available space
2. **Secondary:** Location cards/lists - keep compact and scrollable
3. **Tertiary:** Metadata (distance, hours, ratings)

**Map sizing guidelines:**

- Full-screen experiences: Use `h-[calc(100vh-12rem)]` or similar viewport heights
- Split-view layouts: Map should take at least 60-70% of available space
- Avoid tiny maps - minimum height should be `h-[400px]` on mobile, larger on desktop
- In list+map layouts, prioritize map size on desktop with `flex-1` while list stays fixed width

### Color Usage

- Use semantic colors for status (green=available, blue=active, red=urgent, gray=inactive)
- Keep marker colors consistent within a screen
- Map theme matches app theme automatically

### Mobile Optimization

- Stack map above/below list on mobile
- Side-by-side on desktop: `flex-col lg:flex-row`
- Use `h-[400px] lg:h-auto` for responsive heights
- Minimum 44×44px touch targets for markers

### Performance

- Use `MapClusterLayer` for 50+ points
- Consider lazy-loading maps
- Show loading states

## Map Component Quick Reference

**Components:**

- `<Map>` - Main container (requires center, zoom, height)
- `<MapMarker>` - Place markers
- `<MarkerContent>` - Custom marker appearance
- `<MarkerPopup>` - Click to open details
- `<MarkerLabel>` - Permanent text label
- `<MapRoute>` - Draw paths (only `coordinates` and `color` props)
- `<MapClusterLayer>` - Automatic clustering
- `<MapControls>` - Zoom, locate, fullscreen buttons

**Critical reminders:**

- Coordinates: `[longitude, latitude]` format
- Container needs explicit height
- All markers MUST be circles with white borders
- No API key required
- Theme-aware (no style toggles needed)

## Related Documentation

For detailed Map component API:

- Map component docs: `src/components/map/README.md`
- Map examples: `src/components/map/map.stories.tsx`
