Styling

This section explains some different ways to style HTML within Marko. From simple inline styles to powerful techniques like CSS Modules for organized and maintainable stylesheets.

Inline Styles

Marko enhances the HTML <style> tag to be processed and optimized by the bundler used in the project. A template may specify any number of <style> tags.

By default, all styles defined in the template are globally scoped. As such, many Marko projects use patterns like BEM to avoid name conflicts.

<style>
  
  /* Bundled and loaded once */
  .fancy {
    color: green;
  }

</style>
<div class="fancy">Hello!</div>
<style>
  /* Bundled and loaded once */
  .fancy {
    color: green;
  }
</style>

<div class="fancy">Hello!</div>
style
  --


    /* Bundled and loaded once */
    .fancy {
      color: green;
    }


  --
div class="fancy" -- Hello!
style
  --

    /* Bundled and loaded once */
    .fancy {
      color: green;
    }

  --

div class="fancy" -- Hello!

The <style> may include a file extension to enable css preprocessors such as scss and less.

<style.scss>
  
  $primary-color: green;

  .fancy-scss {
    color: $primary-color;
  }

</style>
<div class="fancy-scss">Hello!</div>
<style.less>
  
  @primary-color: blue;

  .fancy-less {
    color: @primary-color;
  }

</style>
<div class="fancy-less">Hello!</div>
<style.scss>
  $primary-color: green;

  .fancy-scss {
    color: $primary-color;
  }
</style>

<div class="fancy-scss">Hello!</div>

<style.less>
  @primary-color: blue;

  .fancy-less {
    color: @primary-color;
  }
</style>

<div class="fancy-less">Hello!</div>
style.scss
  --


    $primary-color: green;

    .fancy-scss {
      color: $primary-color;
    }


  --
div class="fancy-scss" -- Hello!
style.less
  --


    @primary-color: blue;

    .fancy-less {
      color: @primary-color;
    }


  --
div class="fancy-less" -- Hello!
style.scss
  --

    $primary-color: green;

    .fancy-scss {
      color: $primary-color;
    }

  --

div class="fancy-scss" -- Hello!

style.less
  --

    @primary-color: blue;

    .fancy-less {
      color: @primary-color;
    }

  --

div class="fancy-less" -- Hello!

Inline CSS Modules

If the <style> tag has a Tag Variable, it leverages CSS Modules to expose its classes as an object.

<style/styles>
  
  .foo { border: 1px solid red }
  .bar { background: green }

</style>
<div class=styles.foo/>
<div class=[styles.foo, styles.bar]/>
<div class={
  [styles.bar]: true
}/>
<div class=styles.foo/>
<style/styles>
  .foo { border: 1px solid red }
  .bar { background: green }
</style>

<div class=styles.foo/>
<div class=[styles.foo, styles.bar]/>
<div class={ [styles.bar]: true }/>
<div.${styles.foo}/>
style/styles
  --


    .foo { border: 1px solid red }
    .bar { background: green }


  --
div class=styles.foo
div class=[styles.foo, styles.bar]
div class={
  [styles.bar]: true
}
div class=styles.foo
style/styles
  --

    .foo { border: 1px solid red }
    .bar { background: green }

  --

div class=styles.foo
div class=[styles.foo, styles.bar]
div class={ [styles.bar]: true }
div.${styles.foo}

This approach allows for scoping of CSS class names without needing to follow naming conventions such as BEM.

You may still provide a custom file extension to enable to use of preprocessors.

<style.scss/styles>
  
  $primary-color: green;

  .fancy {
    color: $primary-color;
  }

</style>
<div class=styles.fancy>Hello</div>
<style.scss/styles>
  $primary-color: green;

  .fancy {
    color: $primary-color;
  }
</style>

<div class=styles.fancy>Hello</div>
style.scss/styles
  --


    $primary-color: green;

    .fancy {
      color: $primary-color;
    }


  --
div class=styles.fancy -- Hello
style.scss/styles
  --

    $primary-color: green;

    .fancy {
      color: $primary-color;
    }

  --

div class=styles.fancy -- Hello

Dynamic Values

A <style> tag may contain dynamic values, written as ${...} interpolations, keeping state-driven styling next to the rest of the stylesheet.

<let/hue=200>
<style>
  
  .swatch {
    /* Updates whenever `hue` changes */
    background: hsl(${hue} 80% 50%);
    border-radius: 4px;
  }

</style>
<input type="range" min="0" max="360" value:parseFloat:=hue>
<div class="swatch"/>
<let/hue=200>

<style>
  .swatch {
    /* Updates whenever `hue` changes */
    background: hsl(${hue} 80% 50%);
    border-radius: 4px;
  }
</style>

<input type="range" min="0" max="360" value:parseFloat:=hue>
<div class="swatch"/>
let/hue=200
style
  --


    .swatch {
      /* Updates whenever `hue` changes */
      background: hsl(${hue} 80% 50%);
      border-radius: 4px;
    }


  --
input type="range" min="0" max="360" value:parseFloat:=hue
div class="swatch"
let/hue=200

style
  --

    .swatch {
      /* Updates whenever `hue` changes */
      background: hsl(${hue} 80% 50%);
      border-radius: 4px;
    }

  --

input type="range" min="0" max="360" value:parseFloat:=hue
div class="swatch"

The stylesheet is still extracted and bundled once. Each interpolation compiles to a CSS custom property whose value Marko keeps up to date as state changes.

Note

Dynamic values are only supported where css expects a declaration value, and apply to elements rendered after the <style> tag. See the <style> reference for details.

Per-Item Values

Because dynamic values apply to the elements after each <style> tag, placing one inside a <for> gives every iteration its own values. Each pass renders its own <style> just before that item's markup, so the interpolations resolve per item.

<nav class="brands">
  <for|brand| of=input.brands>
    <style>
      
      .brand {
        color: ${brand.color};
      }
    
    </style>
    <a class="brand" href=brand.url>${brand.name}</a>
  </for>
</nav>
<nav class="brands">
  <for|brand| of=input.brands>
    <style>
      .brand {
        color: ${brand.color};
      }
    </style>
    <a class="brand" href=brand.url>${brand.name}</a>
  </for>
</nav>
nav class="brands"
  for|brand| of=input.brands
    style
      --


            .brand {
              color: ${brand.color};
            }

         ${" "}
      --
    a class="brand" href=brand.url -- ${brand.name}
nav class="brands"
  for|brand| of=input.brands
    style
      --

            .brand {
              color: ${brand.color};
            }
         ${" "}
      --
    a class="brand" href=brand.url -- ${brand.name}
Warning

Each iteration renders a real <style> element among the items, so positional selectors like :nth-child count those elements and miss their targets. Use :nth-of-type, which counts only elements of the same tag and skips the interleaved <style> tags.

Auto-Discovered Styles

Styling files adjacent a custom tag are automatically discovered. These files are imported and processed the same as inline styles.

style.css
.fancy {
  color: green;
}
index.marko
<div class="fancy">Hello!</div>
div class="fancy" -- Hello!
Tip

When a template is becoming too large it can be helpful to pull its styling into an associated style file such as this.

Imported Styles

Styles may also be imported.

fancy.css
.fancy {
  color: red;
}
index.marko
import "./fancy.css";
<div class="fancy">Hello!</div>
import "./fancy.css";

<div class="fancy">Hello!</div>
import "./fancy.css";
div class="fancy" -- Hello!
import "./fancy.css";

div class="fancy" -- Hello!
Tip

Although generally inline or auto-discovered styles are preferred, importing styles can be helpful when sharing across templates.

Imported CSS Modules

CSS Module files may also be imported.

something.module.css
.fancy {
  color: red;
}
index.marko
import * as styles from "./something.module.css";
<div class=styles.fancy/>
import * as styles from "./something.module.css";
div class=styles.fancy
Caution

Since most bundlers are configured by default to support css modules for *.module.css files, this should work out of the box. If it is not supported by a bundler there is almost certainly a plugin.


Contributors

Helpful? You can thank these awesome people! You can also edit this doc if you see any issues or want to improve it.