---
title: "Layer Controls"
description: "VControlLayer and VControlLayerGroup - Layer visibility toggle and opacity slider controls for MapLibre and deck.gl layers"
canonical_url: "https://v-maplibre.geoql.in/controls/layer"
---
# Layer Controls

> VControlLayer and VControlLayerGroup - Layer visibility toggle and opacity slider controls for MapLibre and deck.gl layers

<callout icon="i-lucide-layers" target="_blank" to="https://mapcn-vue.geoql.in/examples/maplibre-layer-control">

**Live Demo** - Try the layer controls with multiple layer types

</callout>

# VControlLayer

## Overview

The `VControlLayer` component provides an interactive control for managing layer visibility and opacity. It supports both MapLibre native layers and deck.gl layers, automatically detecting the layer type.

Features:

- Visibility toggle (show/hide layer)
- Opacity slider (0-100%)
- Auto-detection of layer type (MapLibre vs deck.gl)
- Support for fill, line, circle, and symbol layers
- v-model bindings for reactive state management

## Usage

```vue
<script setup lang="ts">
  import { VMap, VControlLayer, VLayerMaplibreGeojson } from '@geoql/v-maplibre';

  const mapOptions = {
    style: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
    center: [-74.5, 40],
    zoom: 9,
  };

  const geojsonSource = {
    type: 'geojson',
    data: {
      type: 'FeatureCollection',
      features: [
        {
          type: 'Feature',
          geometry: { type: 'Point', coordinates: [-74.5, 40] },
          properties: {},
        },
      ],
    },
  };

  const layerSpec = {
    id: 'points-layer',
    type: 'circle',
    paint: {
      'circle-radius': 10,
      'circle-color': '#007cbf',
    },
  };
</script>

<template>
  <VMap :options="mapOptions" style="height: 500px">
    <VLayerMaplibreGeojson
      source-id="points"
      layer-id="points-layer"
      :source="geojsonSource"
      :layer="layerSpec"
    />
    <VControlLayer
      layer-id="points-layer"
      position="top-right"
      title="Points Layer"
    />
  </VMap>
</template>
```

## Props

### layerId

- **Type:** `string`
- **Required:** `true`

The ID of the layer to control. Must match either a MapLibre layer ID or a deck.gl layer ID.

### position

- **Type:** `'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'`
- **Required:** `false`
- **Default:** `'top-right'`

Position of the control on the map.

### visible

- **Type:** `boolean`
- **Required:** `false`
- **Default:** `true`

Initial visibility state of the layer. Supports v-model binding with `v-model:visible`.

### opacity

- **Type:** `number`
- **Required:** `false`
- **Default:** `1`

Initial opacity of the layer (0-1). Supports v-model binding with `v-model:opacity`.

### title

- **Type:** `string`
- **Required:** `false`
- **Default:** `'Layer Control'`

Title displayed in the control panel.

### layerType

- **Type:** `'maplibre' | 'deckgl'`
- **Required:** `false`
- **Default:** Auto-detected

Force the layer type. If not specified, the component auto-detects whether the layer is a MapLibre or deck.gl layer.

## Events

### @visibility-change

Emitted when the layer visibility changes.

- **Payload:** `boolean`

### @opacity-change

Emitted when the layer opacity changes.

- **Payload:** `number`

### @update:visible

Emitted for v-model:visible binding.

- **Payload:** `boolean`

### @update:opacity

Emitted for v-model:opacity binding.

- **Payload:** `number`

## v-model Bindings

The component supports two-way binding for both visibility and opacity:

```vue
<script setup lang="ts">
  import { ref } from 'vue';

  const isVisible = ref(true);
  const layerOpacity = ref(1);
</script>

<template>
  <VControlLayer
    layer-id="my-layer"
    v-model:visible="isVisible"
    v-model:opacity="layerOpacity"
  />

  <p>Visible: {{ isVisible }}</p>
  <p>Opacity: {{ layerOpacity }}</p>
</template>
```

## Layer Type Support

### MapLibre Layers

For MapLibre native layers, the control automatically maps to the correct opacity property:

<table>
<thead>
  <tr>
    <th>
      Layer Type
    </th>
    
    <th>
      Opacity Property
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        fill
      </code>
    </td>
    
    <td>
      <code>
        fill-opacity
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        line
      </code>
    </td>
    
    <td>
      <code>
        line-opacity
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        circle
      </code>
    </td>
    
    <td>
      <code>
        circle-opacity
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        symbol
      </code>
    </td>
    
    <td>
      <code>
        icon-opacity
      </code>
    </td>
  </tr>
</tbody>
</table>

### deck.gl Layers

For deck.gl layers, the control uses the layer's `visible` and `opacity` props. The component clones the layer with updated properties using the immutable pattern required by deck.gl.

## With deck.gl Layers

```vue
<script setup lang="ts">
  import { VMap, VControlLayer } from '@geoql/v-maplibre';
  import { VLayerDeckglScatterplot } from '@geoql/v-maplibre/deck.gl';

  const mapOptions = {
    style: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
    center: [-122.4, 37.8],
    zoom: 11,
  };

  const data = [
    { coordinates: [-122.4, 37.8], size: 100 },
    { coordinates: [-122.5, 37.7], size: 200 },
  ];
</script>

<template>
  <VMap :options="mapOptions" style="height: 500px">
    <VLayerDeckglScatterplot
      id="scatter-layer"
      :data="data"
      :get-position="(d) => d.coordinates"
      :get-radius="(d) => d.size"
      :get-fill-color="[255, 140, 0]"
    />
    <VControlLayer
      layer-id="scatter-layer"
      position="top-left"
      title="Scatter Points"
    />
  </VMap>
</template>
```

## TypeScript

```typescript
import type { ControlPosition, LayerType } from '@geoql/v-maplibre';

// Props interface
interface LayerControlOptions {
  layerId: string;
  position?: ControlPosition;
  visible?: boolean;
  opacity?: number;
  title?: string;
  layerType?: LayerType;
}
```

---

# VControlLayerGroup

The `VControlLayerGroup` component provides a collapsible panel for controlling multiple layers at once. Each layer gets its own visibility toggle and opacity slider within a single unified control.

## Overview

Use `VControlLayerGroup` when you need to:

- Control multiple related layers in one place
- Provide a cleaner UI than multiple individual `VControlLayer` components
- Allow users to collapse/expand the layer panel

Features:

- Collapsible panel header
- Per-layer visibility toggle and opacity slider
- Support for mixed MapLibre and deck.gl layers
- Configurable layer list via props

## Usage

```vue
<script setup lang="ts">
  import { VMap, VControlLayerGroup, VLayerMaplibreGeojson } from '@geoql/v-maplibre';

  const mapOptions = {
    style: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
    center: [-96, 37.8],
    zoom: 4,
  };

  const statesGeoJson = ref(null);
  // ... fetch GeoJSON data

  const layerGroupConfig = [
    { id: 'states-fill', title: 'US States', visible: true, opacity: 0.5 },
    { id: 'states-outline', title: 'State Borders', visible: true, opacity: 1 },
  ];
</script>

<template>
  <VMap :options="mapOptions" style="height: 500px">
    <VLayerMaplibreGeojson
      source-id="states"
      layer-id="states-fill"
      :source="{ type: 'geojson', data: statesGeoJson }"
      :layer="{
        id: 'states-fill',
        type: 'fill',
        source: 'states',
        paint: { 'fill-color': '#627BC1', 'fill-opacity': 0.5 }
      }"
    />
    <VLayerMaplibreGeojson
      source-id="states"
      layer-id="states-outline"
      :source="{ type: 'geojson', data: statesGeoJson }"
      :layer="{
        id: 'states-outline',
        type: 'line',
        source: 'states',
        paint: { 'line-color': '#627BC1', 'line-width': 2 }
      }"
    />
    <VControlLayerGroup
      :layers="layerGroupConfig"
      title="Map Layers"
      position="top-right"
      :collapsible="true"
    />
  </VMap>
</template>
```

## Props

### layers

- **Type:** `LayerConfig[]`
- **Required:** `true`

Array of layer configurations. Each layer config has:

<table>
<thead>
  <tr>
    <th>
      Property
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        id
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      Layer ID (must match MapLibre or deck.gl layer)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        title
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      -
    </td>
    
    <td>
      Display title in the control
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        visible
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Initial visibility
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        opacity
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Initial opacity (0-1)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        type
      </code>
    </td>
    
    <td>
      <code>
        'maplibre' | 'deckgl'
      </code>
    </td>
    
    <td>
      Auto-detected
    </td>
    
    <td>
      Force layer type
    </td>
  </tr>
</tbody>
</table>

### position

- **Type:** `'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'`
- **Required:** `false`
- **Default:** `'top-right'`

Position of the control on the map.

### title

- **Type:** `string`
- **Required:** `false`
- **Default:** `'Layers'`

Title displayed in the panel header.

### collapsible

- **Type:** `boolean`
- **Required:** `false`
- **Default:** `true`

Whether the panel can be collapsed by clicking the header.

### collapsed

- **Type:** `boolean`
- **Required:** `false`
- **Default:** `false`

Initial collapsed state of the panel.

## Events

### @visibility-change

Emitted when a layer's visibility changes.

- **Payload:** `{ layerId: string; visible: boolean }`

### @opacity-change

Emitted when a layer's opacity changes.

- **Payload:** `{ layerId: string; opacity: number }`

### @update:layers

Emitted when the layers array should be updated (for v-model binding).

- **Payload:** `LayerConfig[]`

## Mixed Layer Types

`VControlLayerGroup` supports controlling both MapLibre and deck.gl layers in the same panel:

```vue
<script setup lang="ts">
  const mixedLayers = [
    { id: 'maplibre-fill-layer', title: 'Fill Layer', type: 'maplibre' },
    { id: 'deckgl-scatter-layer', title: 'Scatter Points', type: 'deckgl' },
    { id: 'maplibre-line-layer', title: 'Roads', type: 'maplibre' },
  ];
</script>

<template>
  <VControlLayerGroup
    :layers="mixedLayers"
    title="All Layers"
  />
</template>
```

## TypeScript

```typescript
import type { ControlPosition, LayerType, LayerConfig } from '@geoql/v-maplibre';

// LayerConfig interface
interface LayerConfig {
  id: string;
  title: string;
  visible?: boolean;
  opacity?: number;
  type?: LayerType;
}

// Props interface
interface LayerGroupOptions {
  layers: LayerConfig[];
  position?: ControlPosition;
  title?: string;
  collapsible?: boolean;
  collapsed?: boolean;
}
```


## Sitemap

See the full [sitemap](https://v-maplibre.geoql.in/sitemap.md) for all pages.
