Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Styling console.log() Output Formatting With CSS

By Ben Nadel on

I use console.log() all day, every day. But, I almost always forget that you can use CSS to style the formatting of the console output. As such, I wanted to sit down and write out some console.log() styling examples in an attempt to hammer this information into my caveman brain. Also, it's hump day and I thought this would be a nice little mental palette cleanser.

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

If you look at the Mozilla Developer docs for Console, you'll see that the Console object supports some nifty features like string substitution and CSS styling. Both of these features work by using a special token inside the first console.log() argument followed by a series of subsequent arguments that are used to modify the first argument.

So, for example, we can interpolate an Object using %o like this:

console.log( "My document: %o", document );

And, we can style that same log statement using %c like this:

console.log( "%cMy document: %o", "color: red ;", document );

Notice that the second and third parameters are used to modify the %c and %o respectively. In this case, the color: red affects everything that follows the %c.

With that said, let's have some fun with this and see what it can do:

<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<title>
		Styling console.log() Output Formatting With CSS
	</title>
</head>
<body>

	<h1>
		Styling console.log() Output Formatting With CSS
	</h1>

	<script type="text/javascript">

		console.log(
			"%cAltering the text color",
			"color: fuchsia"
		);

		console.log(
			"%cAltering the text experience",
			"background-color: fuchsia ; color: white ; font-weight: bold ; " +
			"font-size: 20px ; font-style: italic ; text-decoration: underline ; " +
			"font-family: 'american typewriter' ; text-shadow: 1px 1px 3px black ;"
		);

		// NOTE: In this demo, inline-block isn't needed in Chrome; but, it is needed in
		// Firefox or the block properties, like padding, don't work. Trying to use
		// "block" will work in Chrome; but, will go full-width in Firefox.
		console.log(
			"%cAltering the box display",
			"display: inline-block ; border: 3px solid red ; border-radius: 7px ; " +
			"padding: 10px ; margin: 20px ;"
		);

		// NOTE: Background-images work in Chrome, but not in Firefox. Also, at least
		// locally, only fully qualified URLs seems to work (but that may have been
		// something I was messing up).
		// --
		// Also, it doesn't look like width/height work on box-model. As such, I am using
		// padding to push-out the box size.
		console.log(
			"%cBackground image",
			"display: inline-block ; background-image: url( 'https://bennadel.github.io/JavaScript-Demos/demos/console-log-css/rock.png' ) ; " +
			"background-size: cover ; padding: 10px 175px 158px 10px ; " +
			"border: 2px solid black ; font-size: 11px ; line-height: 11px ; " +
			"font-family: monospace ;"
		);

		// The same CSS styling can be used with many of the other console methods, like
		// the .group() method.
		console.group(
			"%cGrouped Output",
			"background-color: #e0005a ; color: #ffffff ; font-weight: bold ; padding: 4px ;"
		);
		console.log( "Groups are cool for grouped stuff." );
		console.log( "Totes magotes" );
		console.groupEnd();

	</script>

</body>
</html>

As you can see, we're just trying to throw a lot of CSS at the various console.log() statements to see what they support. And, when we run the above code in Chrome, we get the following developer tools output:

Console logging with custom CSS styles.

How freaking cool is that! Firefox also supports this; though, Firefox does not appear to support background-image.

Now, this is really cool; but, it's super verbose. What would be really cool is if we packaged this functionality up into something that hides away the complexity of the CSS while still providing the flexibility of console.log(). Luckily, JavaScript is an intensively powerful language. And, wrapping this up isn't actually all that complicated.

ASIDE: When I say "not that complicated", what I means is that we can do this with basic JavaScript mechanics and just a little bit of code. I don't mean to imply that code is trivial.

To have some fun with this idea, I'm going to create an echo object as a replacement for console. Meaning, it will have console-inspired methods like:

  • echo.log()
  • echo.error()
  • echo.warn()
  • echo.trace()
  • echo.group()
  • echo.groupEnd()

And, just like the console method, these echo methods will be variadic; meaning, they accept a dynamic number of arguments. The big difference here is that the echo object will also provide formatting methods that can be used to format one of the inputs to the aforementioned methods:

  • echo.asAlert()
  • echo.asWarning()

... which are intended to be used as inputs to the console(esque) method:

echo.log( echo.asAlert( "Oh no!" ), "Something went wrong!" );

This would format the Oh no! portion with "alert styling"; and, leave the Something went wrong! input as "plain text".

Let's take a look at how this fits together:

<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<title>
		Styling console.log() Output Formatting With CSS
	</title>
</head>
<body>

	<h1>
		Styling console.log() Output Formatting With CSS
	</h1>

	<script type="text/javascript">

		// I provide a logging API that has some special sauce for formatting.
		var echo = (function() {

			var queue = [];
			var ECHO_TOKEN = {};
			var RESET_INPUT = "%c ";
			var RESET_CSS = "";

			// Attach formatting utility method.
			function alertFormatting( value ) {

				queue.push({
					value: value,
					css: "display: inline-block ; background-color: #e0005a ; color: #ffffff ; font-weight: bold ; padding: 3px 7px 3px 7px ; border-radius: 3px 3px 3px 3px ;"
				});

				return( ECHO_TOKEN );

			}

			// Attach formatting utility method.
			function warningFormatting( value ) {

				queue.push({
					value: value,
					css: "display: inline-block ; background-color: gold ; color: black ; font-weight: bold ; padding: 3px 7px 3px 7px ; border-radius: 3px 3px 3px 3px ;"
				});

				return( ECHO_TOKEN );

			}

			// I provide an echo-based proxy to the given Console Function. This uses an
			// internal queue to aggregate values before calling the given Console
			// Function with the desired formatting.
			function using( consoleFunction ) {

				function consoleFunctionProxy() {

					// As we loop over the arguments, we're going to aggregate a set of
					// inputs and modifiers. The Inputs will ultimately be collapsed down
					// into a single string that acts as the first console.log parameter
					// while the modifiers are then SPREAD into console.log as 2...N.
					// --
					// NOTE: After each input/modifier pair, I'm adding a RESET pairing.
					// This implicitly resets the CSS after every formatted pairing.
					var inputs = [];
					var modifiers = [];

					for ( var i = 0 ; i < arguments.length ; i++ ) {

						// When the formatting utility methods are called, they return
						// a special token. This indicates that we should pull the
						// corresponding value out of the QUEUE instead of trying to
						// output the given argument directly.
						if ( arguments[ i ] === ECHO_TOKEN ) {

							var item = queue.shift();

							inputs.push( ( "%c" + item.value ), RESET_INPUT );
							modifiers.push( item.css, RESET_CSS );

						// For every other argument type, output the value directly.
						} else {

							var arg = arguments[ i ];

							if (
								( typeof( arg ) === "object" ) ||
								( typeof( arg ) === "function" )
								) {

								inputs.push( "%o", RESET_INPUT );
								modifiers.push( arg, RESET_CSS );

							} else {

								inputs.push( ( "%c" + arg ), RESET_INPUT );
								modifiers.push( RESET_CSS, RESET_CSS );

							}

						}

					}

					consoleFunction( inputs.join( "" ), ...modifiers );

					// Once we output the aggregated value, reset the queue. This should have
					// already been emptied by the .shift() calls; but the explicit reset
					// here acts as both a marker of intention as well as a fail-safe.
					queue = [];

				}

				return( consoleFunctionProxy );

			}

			return({
				// Console(ish) functions.
				log: using( console.log ),
				warn: using( console.warn ),
				error: using( console.error ),
				trace: using( console.trace ),
				group: using( console.group ),
				groupEnd: using( console.groupEnd ),

				// Formatting functions.
				asAlert: alertFormatting,
				asWarning: warningFormatting
			});

		})();

		// --------------------------------------------------------------------------- //
		// --------------------------------------------------------------------------- //

		// Let's try mixing a bunch of values together.
		echo.log(
			echo.asAlert( "This is great!" ),
			"Right!",
			{ "i am": "an object" },
			null,
			[ "an array" ],
			function amAFunction() {},
			echo.asWarning( "I mean, right?!?!?!" )
		);

		echo.log();

		// Let's try a more sensible example.
		echo.group( echo.asWarning( "Arnold Schwarzenegger Movies" ) );
		echo.log( "The Running Man" );
		echo.log( "Terminator 2", echo.asAlert( "Amazing!" ), echo.asWarning( "TOP 100" ) );
		echo.log( "Predator" );
		echo.log( "Twins", echo.asWarning( "TOP 50" ) );
		echo.groupEnd();

		echo.log();

		echo.log( echo.asAlert( "Winner Winner" ), "Chicken dinner!" );

	</script>

</body>
</html>

The mechanics here are a little hard to pick out immediately. But, basically what is happening is that the "formatting" methods, asAlert() and asWarning(), are pushing values and CSS onto an internal queue. Then, the console proxy methods are consolidating that internal queue down into a single console call.

If we run this demo in the Chrome, we get the following developer tools output:

Console logging with custom CSS styles using echo abstraction.

As you can see, this echo abstraction makes it super easy to seamlessly integrate both styled and unstyled inputs. No need to worry about which argument refers back to which String token - you just wrap your inputs in the various formatters and pass it all the variadic proxy functions.

Anyway, this was mostly a note to self, so I won't go too deep into the weeds here. This was just a lot of fun. And, I hope that by putting some ideas down on paper, so to speak, these features will be top-of-mind when I go to use them next time.



Reader Comments

@Sebastian,

Oh man, Firebug changed my life! Heck, I think Firebug changed the industry. Before that, it was all alert( value ) calls all over the place. Even "FirebugLite" for IE was not bad. It must be something amazing to have contributed to something that left such an impact on the world. Nicely done!!

Reply to this Comment

@Ben,

thank you for the kind words! I can just give the compliments back. With your tips you reach many people all around the world. Especially regarding CF questions, almost all web searches lead to your website. :-)

And it's always good to get to know about specific little features like the features of console.log() and Co., which you may not easily find out about, otherwise. So, keep up the great work!

One little tip from my side regarding console logging, for debugging you don't even need to add console.log()s to your site, as the Chrome and Firefox DevTools support so called "logpoints", see https://developers.google.com/web/updates/2019/01/devtools#logpoints and https://developer.mozilla.org/en-US/docs/Tools/Debugger/Set_a_logpoint.

Sebastian

Reply to this Comment

@All,

This post made me really nostalgic about an npm package called Chalk, which allows for styled output on the Terminal in Node.js. I wanted to follow-up this post, to see if I could create a Chalk-inspired set of chainable stylers off my echo object:

www.bennadel.com/blog/3942-styling-console-log-output-with-a-chalk-inspired-formatter-using-javascript-proxies.htm

This new approach uses JavaScript Proxies to allow for calls like this:

echo.log( echo.black.bgRed.bold.italic.larger( "Large black text on red" ) );

It was, more or less, my first real use of the Proxy class in ES6. Was lots of fun!

Reply to this Comment

@Sebastian,

OOooh snap! That's awesome. The Chrome Dev Tools are just bonkers. I've used the debugger and break-points, but never noticed this option. Thanks for the hot tip!

Reply to this Comment

@Angeli,

When I dip into the Node.js world - which isn't so often as of late - I used to love a module called Chalk. I believe that Chalk is doing that "escape sequence" stuff you mention (and is discussed in your linked article). You're timing is actually quite good as I was just thinking about Chalk this morning (and wrote a follow-up post). Chalk's API was so silky smooth and enjoyable.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.