Tagged Templates: How styled-components work under the hood

styled-components is pretty good but the syntax feels illegally simple

For anyone who’s worked with React at some point, styling often becomes boring.

“Who uses vanilla CSS anymore?”

Enter styled-components, an amazing way to style React components. I’m not going to go through all the great features it has, you can go through its website to know that.

What had me curious was how elegant yet illegal its syntax looked compared to regular JavaScript.

I mean, even for someone who’s experienced in JavaScript, the following syntax looks off:

const Button = styled.button`
    color: black;
    background: ${props => props.bgColor};
`;

A lot of questions come into one’s mind:

Well for the last question I don’t have much to say, I don’t want to start a war. For the 4th question: Yes, it is 100% legal JavaScript. For the rest of the questions, let’s explore the answers to them in this blog post.

Tagged Templates: The Basics

Styled Components use a pattern called Tagged Templates. They are simply functions that are invoked with the following format:

functionName`string in literals`;

`<functionName>``string in literals

Although the syntax might feel illegal, it’s 100% legal to do so.

JavaScript simply passes the string as an Array of strings as the function’s argument.

image.png

What about strings with variables and functions interpolated? We would need those for dynamic styling and run-time computation right?

The solution’s fairly simple. JavaScript passes a single array of strings as the first argument to the function, and the remaining arguments are variables and expressions interpolated into the string.

image.png

So we can essentially do this with our function:

const tagFunction = (stringFragments, ...expressions) => {
	let fullString = '';

	stringFragments.forEach((str, index) => {
        fullString += (str + (expressions[index] || ''));
    });

	return fullString;
}

image.png

This lays the foundation for building a library like Styled components where static strings can be used for property names and their corresponding expressions can be used for dynamic/run-time property values.

Complex Expressions

Styled components also give us the ability to pass a function in place of a simple value to be computed every time a component is rendered.

We would need a very simple modification in our code above to handle complex expressions like objects, arrays and functions.

const resolveExpression = (expression) => {
	if (typeof expression === 'string' || typeof expression === 'number') return expression;
	if (typeof expression === 'function') return expression();
	if (typeof expression === 'object') return JSON.stringify(expression);
}

const tagFunction = (stringFragments, ...expressions) => {
	let fullString = '';

	stringFragments.forEach((str, index) => {
            fullString += (
                str + (resolveExpression(expressions[index]) || '')
            );
        });

	return fullString;
}

image.png

Props in function expression, React Component Generation and applying styles via className

Note: styled-components uses the className prop to apply the styles generated to a component.

Simply have the tagFunction return a JSX expression that can further receive props, then pass those props to any function in the expressions.

Once the functions resolve, use the entire style string generated, create a CSS class with it and apply that class to the div/element you’re creating, and have a style tag adjacent to it containing the CSS class with the style string.

const resolveExpression = (expression, props) => {
	if (typeof expression === "string" || typeof expression === "number")
		return expression;
	if (typeof expression === "function") return expression(props); // ${props => props...whatever}
	if (typeof expression === "object") return JSON.stringify(expression);
};

const styled = {};

// Predefined function
styled.div =
	(stringFragments, ...expressions) =>
	// React component
	(props) => {
		let fullStyleString = '';

		stringFragments.forEach((str, index) => {
			fullStyleString += (
				str + (resolveExpression(expressions[index]) || '')
			);
		});

		return (
			<>
				<style type="text/css">
					{`.<classNameGeneratedAtRunTime> { ${fullStyleString} }`}
				</style>
				<div
					className={`${
						props.className || ""
					} <aboveClassNameGeneratedAtRunTime>`}
				>
					{props.children}
				</div>
			</>
		);
	};

// Usage
const Div = styled.div`
	color: ${(props) => props.black};
`;

<Div $color="black">Hey There</Div>;

Applying Styles for Custom Components

Now the final stage is to apply the styles we have to the custom components we create.

Simply change styled to a function by default that performs the same operation as styled.div above, but on a Component that’s passed.

For example:

const MyComponent = ({ className }) => {
    // The existence of className is important.
    // If your component does not have a className prop it won't work.
    return <div className={className}>My content</div>
}

const StyledMyComponent = styled(MyComponent)`
    color: #000000;
    background: #ffffff;
`;

The snippet to make it all work would look like the following:

const styled =
	// Custom component
	(Component) =>
	// Style Template Literals
	(stringFragments, ...expressions) =>
	// React component
	(props) => {
		let fullStyleString = '';

		stringFragments.forEach((str, index) => {
			fullStyleString += (
				str + (resolveExpression(expressions[index]) || '')
			);
		});

		return (
			<>
				<style type="text/css">
					{`.<classNameGeneratedAtRunTime> { ${fullStyleString} }`}
				</style>
				<Component
					className={`${
						props.className || ""
					} <aboveClassNameGeneratedAtRunTime>`}
				>
					{props.children}
				</Component>
			</>
		);
	};

With this, you should have a clear understanding of how styled-components internally work, it’s a great library that perfectly abstracts a lot of complicated implementation details and makes the best use of a complicated feature of JavaScript to make it elegant and readable for the everyday web developer.