BACK TO HOME

CSS-inJS secret sauce: Understanding Tagged template literals

Published on Sat Aug 29 2020

Tagged template literals feature is the core ingredient of styled-components, emotion and other CSS-in-JS libraries. While it is a relatively newer syntax, it is still used rather heavily with the advent of css-in-js.

The syntax of tagged template literals looks like so. A tagged template literal is used with an expression followed by an interpolation of strings within back-ticks.

const value = taggedTemplateLiteral`
  ...
`

Tagged-template-literals are very easy once we get to know them. They are actually very similar to functions. Let's take this example. This is a simple function name hello which logs a name to the screen.

function hello(name){
  console.log("hello " + name)
}

hello("bhargav")
//hello bhargav

We can do something similar with tagged template literals too. The only difference here is that the name is actually sent within an array of strings and since we only have one homogenous string here, the array has just one value which is the name.

function hello(strings){
  var name = strings[0]
  console.log("hello " + name)
}

hello`bhargav`
//hello bhargav

Mixing strings with objects

Sometimes there are scenarios where we want to generate strings smartly while evaluating objects and variables in scope. For eg: within styled-components, we want to generate the style CSS rules based on the props the component has.

// A blue background button if used as 
// <StyledButton primary>
const StyledButton = styled.button`
  background: ${props => props.primary ? "blue" : "grey"}
`

This requires us to create the style rule background:blue but the rule can't be calculated at the time of component definition. The rule has to be calculated when this component is rendered (A much later time).

The challenge here is to embed(also called interpolation) a function inside a string and evaluate it at a convenient time (in this case, onMount).

Doing this with simple functions is super complicated.

Simple Function call arguments are evaulated first

Simple function arguments are evaluated before they are sent to the function.

So in this case the argument to hello function is "bhargav " + doSomething + " world" and this is evaulated into a string before it is sent to the hello function.

function hello(name){
  console.log("hello " + name)
}

function doSomething(){
  console.log(5)
}

hello("bhargav " + doSomething + " world")
/*
hello bhargav function doSomething(){
   console.log(5)
} world
/*

One solution is to split strings into multiple pieces and pass all of the strings and functions in order, but tagged template literals do it very easily.

Enter tagged template literals

Tagged template literals split the interpolated string into a combination of strings and interpolations. So if we want to pass in a function to the tagged template literal function, we can do it like so.

function doSomething(){
  console.log(5)
}

function hello(strings, doSomething, ...moreInterpolations){
  // you can now access a function intact and 
  // use it as you please
  var name = strings.join("");
  console.log("hello "+name)
  doSomething();  
}

hello`bhargav ${doSomething} world`
//hello bhargav world
// 5

The first argument that the function hello (used as a tagged template literal) receives is an array of all the string pieces and then it receives each interpolated value one by one as arguments. We only have one interpolation here, hence we can directly access that as the second argument. Note: if we had interpolated another value inside the string, the 3rd argument would be that interpolated value.

The powerful thing about this is that, we receive the function intact and we can then do all sorts of things.

1. Delay string concatenation

For eg, we can delay string concatenation by using the interpolated function.

async function delay(timeToWait){
  return new Promise(resolve => setTimeout(resolve, timeToWait))
}

function doSomething(){
  return 3000
}

// use the return value from doSomething
// and wait for that long
async function hello(strings, doSomething){
  const timeToWait = doSomething();
  await delay(timeToWait)
  var name = strings.join("");
  console.log("hello "+name)
}

hello`bhargav ${doSomething} world` //logs hello bhargav world after 3 seconds

/*
Promise {<pending>}  
> hello bhargav world
*/

2. Use as a higher order function

We can also package the logic into another function that we can execute at a later time.

function createGreeting(strings, ...interpolations){
  return function(name){
    // we are simply concatening all strings together
    // and if we encounter a function we are evaluating 
    // that function with the `name` argument passed
    return strings.reduce((acc,current, index)=>{
        let interpolation = interpolations[index]
        if(typeof interpolation === "function"){
          interpolation = interpolation(name)
        }
        interpolation = interpolation || ""
        return `${acc}${current}${interpolation}`
    }, "")
  }
}

const hello = createGreeting`Hello ${name => name}!`
const whatsUp = createGreeting`Hi ${name => name}! What's up?`
const tellTime = createGreeting`Hi ${name => name}! It is year ${(name) => (new Date()).getUTCFullYear()}`

hello("Bhargav") // Hello Bhargav!
whatsUp("Bhargav") // Hi Bhargav! What's up?
tellTime("Bhargav") // Hi Bhargav! It is year 2020

3. Use it with classes

We are no longer limited with interpolations. As an example, we can also use this with classes.

class Animal{
  constructor(greet){
    this.greet = greet
  }
}

function createAnimal(strings, ...interpolations){  
  function greet(){
    return strings.reduce((acc, current, index) => `${acc}${current}${interpolations[index] || ""}`, "")
  }
  return new Animal(greet)
}

const dog = createAnimal`woof! woof!`

const cat = createAnimal`meow!`

dog.greet() // woof!woof!
cat.greet() // meow!

We can use tagged template literals to tag a string with interpolations and use them along side functions, higher order functions and classes too. And since React components are either functions or components we can use them with them as well.

With React Components

This is the usage we are most interested about since this technique is used by styled-components and other CSS-in-JS libraries. Let's create a tagged template literal that looks very similar to the styled function. The styled tagged template literal function usage looks like this,

const color  = "blue";
// Note: Most of the time we use 
// styled.button or styled.div as developers but
// styled.button is actually just an alias for styled("button")
const StyledComponent = styled("button")`
  background: ${color};
`

<StyledComponent /> 
// Rules are added on mount

Implementing an entire styled component feature is a herculean task but we can create a tagged template literal that looks like this except does something much more trivial.

Let's make a tagged template literal that logs a prop to the screen whenever it changes along with some text around it.

import React, { useEffect } from "react";

function propLogger(ComponentToRender) {
  // tagged template literal is actually created inside a function
  // so that `Component` is in scope.
  return function taggedTemplateLiteral(strings, ...interpolations) {
    return function Component(props) {
      const { color } = props;
      useEffect(() => {
        // on mount
        const value = strings.reduce((acc, current, index) => {
          let interpolation = interpolations[index];
          if (typeof interpolation === "function") {
            interpolation = interpolation(props);
          }
          interpolation = interpolation || "";
          return `${acc}${current}${interpolation}`;
        }, "");
        console.log(value);
      }, [props]);
      return <ComponentToRender />;
    };
  };
}

const DivThatLogsProps = propLogger("div")`
  The sky is ${(props) => props.skyColor}.
  I like ${(props) => props.like}.
`;

// it logs "The sky is red" on mount and
// if the skyColor prop changes, it will log
// again.
export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <DivThatLogsProps like="waffles" skyColor="red">
        Hi
      </DivThatLogsProps>
    </div>
  );
}

Here is a demo on codesandbox.

This is how styled-components works too. It is a far more intense library and does so much more and optimises so much more, but ultimately, it is just a function that uses a tagged template literal to tag CSS rules and allows interpolation with props and adds the rule to the StyleSheet when the component mounts or when it updates.

I hope this tutorial was interesting and hope it helps understand the internals of popular CSS-in-JS libraries better!

Sign up to my newsletter

Get latest news and tips in Fullstack web development right into your inbox!

No spam. I promise!

© 2020 Bhargav Ponnapalli. All rights reserved.