Skip to content

Filter symbols by text input

Replicates the MapLibre example Filter symbols by text input. The places and one symbol layer are YAML; the layer’s filter keeps a place while its icon name contains a query state key. The params panel has no free-text control, so the search box is page chrome in a corner slot, and its JS does one thing: write the key with setGlobalStateProperty.

Type "mus" or "th" — places whose type doesn't match disappear
filter-symbols-by-text-input.yaml
# yaml-language-server: $schema=https://docs.maplibre-yaml.org/schema/latest/map.schema.json
# Gallery twin of https://maplibre.org/maplibre-gl-js/docs/examples/filter-symbols-by-text-input/
# The YAML half: the places and ONE symbol layer whose filter reads a
# `query` state key — a place shows while its icon name contains the
# query. The params panel has no free-text control, so the search box is
# page chrome in a corner slot and its JS writes the state key — see
# filter-symbols-by-text-input.js. (maplibre-gl >= 5.6 for global-state.)
type: map
id: filter-symbols-by-text-input
config:
center: [-77.04, 38.907]
zoom: 11.15
mapStyle: "https://tiles.openfreemap.org/styles/bright"
state:
query:
default: ""
layers:
- id: poi
type: symbol
source:
type: geojson
data:
type: FeatureCollection
features:
- { type: Feature, properties: { icon: theatre }, geometry: { type: Point, coordinates: [-77.038659, 38.931567] } }
- { type: Feature, properties: { icon: theatre }, geometry: { type: Point, coordinates: [-77.003168, 38.894651] } }
- { type: Feature, properties: { icon: bar }, geometry: { type: Point, coordinates: [-77.090372, 38.881189] } }
- { type: Feature, properties: { icon: bicycle }, geometry: { type: Point, coordinates: [-77.052477, 38.943951] } }
- { type: Feature, properties: { icon: music }, geometry: { type: Point, coordinates: [-77.031706, 38.914581] } }
- { type: Feature, properties: { icon: music }, geometry: { type: Point, coordinates: [-77.020945, 38.878241] } }
- { type: Feature, properties: { icon: music }, geometry: { type: Point, coordinates: [-77.007481, 38.876516] } }
filter: ["in", ["global-state", "query"], ["get", "icon"]]
layout:
icon-image: ["concat", ["get", "icon"], "_11"]
icon-overlap: always
text-field: ["get", "icon"]
text-font: ["Noto Sans Regular"]
text-size: 11
text-transform: uppercase
text-letter-spacing: 0.05
text-offset: [0, 1.5]
paint:
text-color: "#202"
text-halo-color: "#fff"
text-halo-width: 2

This markup goes inside <ml-map>; children with a slot ride its corner system.

filter-symbols-by-text-input.html
<div slot="top-right" class="gallery-panel">
<input type="search" data-filter-input placeholder="Filter by name" aria-label="Filter places by name" />
</div>
filter-symbols-by-text-input.js
// The JavaScript half: one call, setGlobalStateProperty. The layer's filter
// already reads the `query` state key (see the YAML), so typing only has
// to write the key — no per-layer visibility bookkeeping as upstream does.
const mapEl = document.querySelector("ml-map");
const input = document.querySelector("[data-filter-input]");
input.addEventListener("input", async () => {
const map = await mapEl.mapReady();
map.setGlobalStateProperty("query", input.value.trim().toLowerCase());
});
  • Upstream hides whole layers by id; reading global-state in one filter replaces that bookkeeping with a single state write.
  • ["in", needle, haystack] on strings is a substring test, and an empty query matches everything.
  • Needs maplibre-gl ≥ 5.6 for global-state. For toggles, sliders and selects, the declarative params panel needs no JS — see Filter symbols by toggling a list.