CartoCSS in Mapbox Studio Classic

This guide is a CartoCSS styling reference for designing maps in Mapbox Studio Classic.

Styling data layers

CartoCSS is constructed by applying blocks of rules to elements. Styles are bounded by curly braces {} and contain style properties and values. Selectors allow you restrict styles to specific layers or groups of objects within layers.

By layer ID

Select all of the elements from a single layer with the layer’s ID. To apply the same style to multiple layers, separate the layer IDs with commas.

#layer_name {
  // styles
}

#layer_1,
#layer_2 {
  // styles will apply to all the objects in both layers
}

By layer class

You can also assign classes to layers to select multiple layers. In Mapbox Studio Classic, layer classes are only available for advanced usage.

.roads {
  // styles will apply to all layers
  // with a class of 'roads'
}

Filter selectors

Sometimes you don’t want to style an entire layer the same. For instance, #roads contains motorways, streets, paths, and other types of roads. Instead, you can use filters to modify the selection to style your motorways and paths differently.

Using the inspector tool, click a layer to see it’s attributes and values. You may also find this information from the Layers panel. Filters are declared after a layer selector and inside square brackets or nested inside a larger style block.

#road[class='motorway'] {
  line-join: round;
  line-color: #800, 75;
}

Zoom level filters

Restrict styles to certain zoom levels. This style will only apply when your map is zoomed all the way out to zoom level 0:

#layer[zoom=0] { /* ... */ }

You can specify ranges of zoom levels using two filters:

#layer[zoom>=4][zoom<=10] { /* ... */ }

Valid operators for zoom filters are:

operator function
= equal to
!= not equal to
> greater than
< less than
>= greater than or equal to
<= less than or equal to

You can nest filters to better organize your styles. For example, this style will draw red lines from zoom levels 4 through 10, but the lines will be thicker for zoom levels 8, 9, and 10.

#layer[zoom>=4][zoom<=10] {
  line-color: red;
  line-width: 2;
  [zoom=8] { line-width: 3; }
  [zoom=9] { line-width: 4; }
  [zoom=10] { line-width: 5; }
}

Numeric value comparison filters

The same comparison operators available for the zoom filter can also be used for any numeric column in your data. For example, you might have a population field in a source full of city points. You could create a style that only labels cities with a population of more than 1 million.

#cities[population>1000000] {
  text-name: [name];
  text-face-name: 'Open Sans Regular';
}

You could also combine multiple numeric filters with zoom level filters to gradually bring in more populated cities as you zoom in.

#cities {
  [zoom>=4][population>1000000],
  [zoom>=5][population>500000],
  [zoom>=6][population>100000] {
    text-name: [name];
    text-face-name: 'Open Sans Regular';
  }
}

As with zoom levels, you can select data based on numeric ranges.

#cities[population>100000][population<2000000] { /* ... */ }

Text comparison filters

You can also filter columns that contain text. Use the equals operator (=) to filter exact matches or get the inverse results with the not-equal operator (!=). Unlike zoom and numeric values, text values must be quoted with either double or single quotes.

For an example, look at the roads layer in Mapbox Streets. It contains an attribute called class, and each value for this field is one of just a few options such as “motorway”, “main”, and “street”.

#roads {
  [class='motorway'] {
    line-width: 4;
  }
  [class='main'] {
    line-width: 2;
  }
  [class='street'] {
    line-width: 1;
  }
}

To select everything that is not a motorway you can use the != (“not equal”) operator in the filter:

#roads[class!='motorway'] { /* ... */ }

Regular expression filters

Note: This is an advanced feature that may have negative performance implications.

You can match text in filters based on a pattern using the regular expression operator (=~). This filter will match any text starting with ‘motorway’ (ie, both ‘motorway’ and ‘motorway_link’).

#roads[class=~'motorway.*'] { /* ... */ }

The . represents ‘any character’, and the * means ‘any number of occurrences of the preceding expression. So .* used in combination means ‘any number of any characters’.

Styling labels

Basic point labels

In CartoCSS, all the properties beginning with text- are used for labeling. All text-related styles must have the following properties:

  1. text-name specifies what text goes in the labels.
  2. text-face-name specifies the typeface(s) that will be used to draw the label. (You can see which typefaces are available from the Fonts panel.)

The text-name property can pull text from your layer’s data fields. If your layer contains a column called name_en, a simple label style would look like this:

#place_label {
  text-name: [name_en];
  text-face-name: 'Open Sans Condensed Bold';
}

By default, the color and size of these labels are black and 10px, respectively. You can change these values with the text-fill and text-size properties.

#place_label {
  text-name: [name_en];
  text-face-name: 'Open Sans Condensed Bold';
  text-fill: #036;
  text-size: 20;
}

To separate your text from the background, add an outline or halo around the text. You can control the color with text-halo-fill and the width of the halo (in pixels) is controlled with text-halo-radius. In the example below, we are using the fadeout color function to make the white halo 30% transparent.

#place_label {
  text-name: [name_en];
  text-face-name: 'Open Sans Condensed Bold';
  text-fill: #036;
  text-size: 20;
  text-halo-fill: fadeout(white, 30%);
  text-halo-radius: 2.5;
}

Text along lines

You can style labels that follow a line such as a road or a river. To do this adjust add the text-placement property. The default is point, but change the value to line to make the text follow along the path of the element.

#waterway_label {
  text-name: [name_en];
  text-face-name: 'Open Sans Condensed Bold';
  text-fill: #036;
  text-size: 20;
  text-placement: line;
}

For rivers it’s nice to have the label offset parallel to the line of the river. You can do this with the text-dy property to specify how large (in pixels) this offset should be. (dy refers to a d__isplacement along the __y axis.)

You can also adjust the text-max-char-angle-delta property. This allows you to specify the maximum line angle (in degrees) that the text should try to wrap around. The default is 22.5°; setting it lower will make the labels appear along straighter parts of the line.

#waterway_label {
  text-name: [name_en];
  text-face-name: 'Open Sans Condensed Bold';
  text-fill: #036;
  text-size: 20;
  text-placement: line;
  text-dy: 12;
  text-max-char-angle-delta: 15;
}

Custom text

Labels aren’t limited to pulling text from just one field. You can combine data from many fields as well as text to construct your text-name. For example you can include a point’s type in parentheses.

#poi_label {
  text-name: [name_en] + ' (' + [type] + ')';
  text-face-name: 'Open Sans Condensed Bold';
  text-size: 16;
}

Other potential uses:

  • Multilingual labels: [name] + '(' + [name_en] + ')'
  • Administrative units: [city] + ', ' + [province]
  • Numeric units: [elevation] + 'm'
  • Clever unicode icons: '⚑ ' + [embassy_name] or '⚓ ' + [harbour_name]

You can also assign any text to labels that does not come from a data field. Due to a backwards-compatibility issue, you will need to quote such text twice for this to work correctly.

#poi_label[maki='park'] {
  text-name: "'Park'";
  text-face-name: 'Open Sans Regular';
}

If you need to include quotation marks in your custom quoted text, you will need to escape them with a backslash. For example, for the custom text City’s “Best” Coffee:

text-name: "'City\'s \"Best\" Coffee'";

Multi-line labels

You can wrap long labels onto multiple lines with the text-wrap-width property which specifies at what pixel width labels should start wrapping. By default the first word that crosses the wrap-width threshold will not wrap - to change this you can set text-wrap-before to true.

#poi_label {
  text-name: [name];
  text-face-name: 'Open Sans Condensed Bold';
  text-size: 16;
  text-wrap-width: 150;
  text-wrap-before: true;
}

Note that text wrapping is not yet supported with text-placement: line.

You may have a specific point where you want the line breaks to happen. You can use \n to indicate a new line.

#poi_label {
  text-name: [name] + '\n' + [type];
  text-face-name: 'Open Sans Condensed Bold';
  text-size: 16;
}

Styling lines

You can apply line styles to both line and polygon layers. The simplest line have just a line-width (in pixels) and a line-color making a single solid line. The default values for these properties are 1 and black respectively.

#admin[admin_level=2] {
  line-width: 0.75;
  line-color: #426;
}

Dashed lines

You can create simple dashed lines with the line-dasharray property. The value of this property is a comma-separated list of pixel widths that will alternatively be applied to dashes and spaces. This style draws a line with 5 pixel dashes separated by 3 pixel spaces:

#admin[admin_level>=3] {
  line-width: 0.5;
  line-color: #426;
  line-dasharray: 5, 3;
}

You can make your dash patterns as complex as you want so long as the dasharray values are all whole numbers.

#admin[admin_level>=3] {
  line-width: 0.5;
  line-color: #426;
  line-dasharray: 10, 3, 2, 3;
}

Caps and joins

With thicker line widths you’ll notice long points at sharp angles and odd gaps on small polygons.

#admin::bigoutline {
  line-color: white;
  line-width: 15;
}

You can adjust the angles with the line-join property; you can round or square them off (the default is called miter). Fill the gaps by setting line-cap to round or square (the default is butt).

#admin::bigoutline {
  line-color: white;
  line-width: 15;
  line-join: round;
  line-cap: round;
}

For dashed lines, line-caps are applied to each dash and their additional length is not included in the dasharray definition. Notice how the following style creates almost-solid lines despite the dasharray defining a gap of 4 pixels.

#admin {
  line-width: 4;
  line-cap: round;
  line-dasharray: 4, 4;
}

Compound line styles

Roads

For certain types of line styles, you may want to style and overlap multiple line styles. For example, a road with casing:

#road[class='motorway'] {
  ::case {
    line-width: 5;
    line-color: #d83;
  }
  ::fill {
    line-width: 2.5;
    line-color: #fe3;
  }
}

Things can get a little more complicated when working with multiple road classes. You can either group your styles by class or group them by attachment. Here we’ve grouped by class (filtering on the class attribute).

#road {
  [class='motorway'] {
    ::case {
      line-width: 5;
      line-color: #d83;
    }
    ::fill {
      line-width: 2.5;
      line-color: #fe3;
    }
  }
  [class='main'] {
    ::case {
      line-width: 4.5;
      line-color: #ca8;
    }
    ::fill {
      line-width: 2;
      line-color: #ffa;
    }
  }
}

Railroads

A common way of symbolizing railroad lines is with regular hatches on a thin line. You can do this with two line attachments - one thin and solid, the other thick and dashed. The dash should be short with wide spacing.

#road[class='major_rail'] {
  ::line,
  ::hatch { line-color: #777; }

  ::line { line-width: 1; }

  ::hatch {
    line-width: 4;
    line-dasharray: 1, 24;
  }
}

Another common railroad line style has a thin dash and a thick outline. Make sure you define the ::dash after the ::line so that it appears on top correctly.

#road[class='major_rail'] {
  ::line {
    line-width: 5;
    line-color: #777;
  }
  ::dash {
    line-color: #fff;
    line-width: 2.5;
    line-dasharray: 6, 6;
  }
}

Tunnels

You can create a simple tunnel style by modifying a regular road style and making the background line dashed.

#road,
#bridge {
  ::case {
    line-width: 8;
    line-color: #888;
  }
  ::fill {
    line-width: 5;
    line-color: #fff;
  }
}

#tunnel {
  ::case {
    line-width: 8;
    line-color: #888;
    line-dasharray: 4, 3;
  }
  ::fill {
    line-width: 5;
    line-color: #fff;
  }
}

Line patterns with images

Certain types of line patterns are too complex to achieve with regular compound line styles. Mapbox Studio Classic allows you to use repeated images alongside or in place of your other line styles. As an example, we’ll make a pattern that we’ll use to represent a cliff. To do this you’ll need to work with external graphics software - we’ll be using Inkscape.

In Inkscape (or whatever you are using), create a new document. The size should be rather small - the height of the image will be the width of the line pattern and the width of the image will be repeated along the length of the line. Our example is 30×16 pixels.

Note how the centerline of the pattern is centered on the image (with empty space at the top) for correct alignment with the line data.

To use the image from Inkscape, export it as a PNG and add it to your style’s .tm2 folder. To apply the image to an element, use the line-pattern-file property:

#barrier_line[class='cliff'] {
  line-pattern-file: url('cliff.png');
}

For some patterns, such as the cliff in this example, the direction of the pattern is important. The bottom of line pattern image will be on the right side of lines. The left side of the image will be at the beginning of the line.

Styling polygons

Polygons are a series of connected lines that can be filled with a solid color or a pattern, and also given an outline.

Tip: everything covered in the styling lines guide can also be applied to polygon layers.

Basic styling

The simplest polygon style is a solid color fill.

#landuse[class='park'] {
  polygon-fill: #bda;
}

If you want to adjust the opacity of polygon-fill you can use the polygon-opacity property. The value of this property is a number between 0 and 1, where 0 is fully transparent and 1 is fully opaque. A lower opacity allows you to see overlapping shapes in the same layer.

#landuse[class='park'] {
  polygon-fill: #bda;
  polygon-opacity: 0.5;
}

Gaps and gamma

When you have a layer containing polygons that should fit together seamlessly, you might notice subtle gaps between them at certain scales. Use the polygon-gamma style to help reduce this effect. The property accepts a value between 0 and 1. The default is 1, so try lowering it to hide the gaps. Be careful about setting it too low, as you may get jagged edges and undesired artifacts.

#water {
  polygon-fill: #acf;
  polygon-gamma: 0.5;
}

Patterns and textures

With CartoCSS, you can fill areas with textures and patterns by bringing in external images. You can create the patterns yourself with image editing software, or find ready-made images from resource websites such as Subtle Patterns or Free Seamless Textures.

You can add a pattern style from any local file using the polygon-pattern-file style. Here’s a simple diagonal stripe pattern you can use to try out - you can reference it from CartoCSS as in the snippet below. Save the file to your style’s .tm2 folder.

pattern-stripe

#landuse[class='park'] {
  polygon-pattern-file: url('pattern-stripe.png');
}

Any images referenced in CartoCSS must be saved in your style’s .tm2 folder, otherwise your images will not be uploaded with your style. If your project uses a lot of images, we recommend creating a subdirectory in your style’s .tm2 folder and include the subdirectory in the path name. For example, if you create a subdirectory named images:

#landuse[class='park'] {
  polygon-pattern-file: url('images/pattern-stripe.png');
}

Background patterns

If you want to add a pattern image to the background of the whole map, you can use the background-image property on the Map element.

Map {
  background-image: url('pattern.png');
}

Like all other properties on the Map element, the styles are global and cannot be filtered or changed depending on zoom level.

Combining patterns and fills

You can use transparency or compositing operations to get a lot of variety out of a single pattern image.

Ensuring seamlessness

There are two types of pattern alignment: local (the default) and global (specified with polygon-pattern-alignment: global;).

When a pattern style has local alignment, that means that the pattern will be aligned separately for each polygon it is applied to. The top-left corner of the pattern image will be aligned to the top-left corner of a polygon’s bounding box.

When a pattern style has global alignment, pattern images are aligned to the image tile instead of the individual geometries. Thus a repeated pattern will line up across all of the polygons it’s applied to. With global alignment, pattern images should not be larger than the metatile (excluding the buffer), otherwise portions of the pattern will never be shown.

Also keep in mind that the pixel dimensions of the image file must multiply evenly up to the width and height of the tile, 256×256 pixels. Your pattern width or height dimensions could be 16 or 32 or 128, but should not be 20 or 100 or any other number that you can’t evenly divide 256 by. If you are using patterns from a resource website, you may need to resize them in an image editor to conform to this limitation.

Additional questions? Ask our support team or learn more about How Mapbox Works.