Style model
Keep styling intent in the right layer
Tileflow separates basemap defaults, human theme colors, module-specific tokens, semantic style modules, icon resources, and raw MapLibre layer patches. That separation keeps simple maps simple while still allowing exact control when a product needs it.
Levels
The public styling API has six levels
Start at the top and move down only when you need more precision. Most product maps should use basemap, theme.colors, icons, and a small number of module tokens.
basemaposm() chooses the data source defaults, glyphs, sprites, attribution, and OSM Bright layer template.theme.colorsHuman-scale tokens for common map decisions: water, park, building, road, roadMajor, roadCasing, boundary, text, and halos.theme.modulesGranular advanced tokens scoped to a domain. theme.modules.roads.rail only changes rail; missing keys preserve the basemap default.maps.<name>.modulesSemantic behavior such as labels(), poi(), and roads(). Modules control behavior while themes own colors.maps.<name>.iconsA reusable icon set, hosted MapLibre sprite, or local folder consumed by POI layers and future overlays.layersExact MapLibre paint/layout/filter patches for the cases where tokens and modules are not precise enough.Rules
Rules that should not drift between modules
These rules define how new modules should behave. They are intentionally conservative: missing values keep the basemap style, and advanced tokens only affect the key the user actually set.
Missing colorsA missing token keeps the basemap default instead of deriving a whole new palette.Base road colorsroad, roadMajor, and roadCasing cover the common road changes. They do not recolor rail, ferry, path, or cablecar layers.Advanced road colorstheme.modules.roads is per-key. primary does not recolor trunk; rail does not recolor highways.Map moduleslabels(), poi(), and roads() control their domain without changing unrelated basemap layers.IconsMap icons complement the basemap sprite. Local folders compile to MapLibre sprite.json/png in dev/build; hosted deploy uses sprite URLs.Legacy aliasesaccent, canvas, greenspace, fontFamily, and fonts are compatibility aliases. New configs should avoid them.Example
Use the smallest control that expresses the intent
Use roads() for road detail and hierarchy, poi() for semantic POI control, and layers for exact MapLibre patches. Modules adjust their own domain without taking ownership of theme colors or unrelated basemap layers.
import { defineTileflow, labels, osm, poi, roads, styleOverride } from "@tileflow/core";
export default defineTileflow({
icons: {
brand: {
sprite: "https://cdn.example.com/tileflow/brand/sprite",
},
},
themes: {
light: {
colors: {
water: "#8ed6e8",
park: "#c3f1d5",
road: "#ffffff",
roadMajor: "#f5d58a",
roadCasing: "#dde0e3",
text: "#566371",
textHalo: "#ffffff",
},
modules: {
labels: {
road: "#7a8794",
water: "#4b8fa8",
},
poi: {
food: "#d97706",
culture: "#7c3aed",
transit: "#2563eb",
},
roads: {
rail: "#6f7780",
},
},
},
},
maps: {
madrid: {
basemap: osm(),
theme: "light",
icons: "brand",
modules: [
labels({ roads: "minimal" }),
poi({ categories: ["food", "coffee", "culture", "transit"], icons: "essential" }),
],
layers: {
"highway-primary": {
paint: { "line-opacity": 0.9 },
},
},
},
roadBehavior: {
basemap: osm(),
theme: "light",
modules: [
roads({
preset: "navigation",
detail: "streets",
casing: "strong",
}),
],
},
patched: {
basemap: osm(),
theme: "light",
modules: [
styleOverride({
layers: {
background: {
paint: { "background-color": "#f8f7f7" },
},
},
}),
],
},
},
});