Fitting Text to a Container

There are a number of ways to go about putting some text in a container and having it size itself to fill that container. There are different technologies we can use and different considerations to think about. Let us count the ways.

Magic Number it with viewport units

If you set type with vw (viewport width) units, you can find an exact number where the text pretty closely fits the container and doesn’t break as you resize. I’d call this a magic number.

In this case, font-size: 25.5vw; works down to a 320px viewport, but still will break much lower than that.

See the Pen Fitted Text with Viewport Units by Chris Coyier (@chriscoyier) on CodePen.

This is kind of a less exotic version of fluid typography, which involves more of a sprinkling of viewport units and min/max sizes.


Dave Rupert’s FitText is up for the job. You still need a bit of a magic number to get the sizing just right for any particular job:

See the Pen Fitted Text with FitText by Chris Coyier (@chriscoyier) on CodePen.

FitText without jQuery

If you aren’t using jQuery, there are options. Listed from the repo:

  • non-jQuery FitText from @adactio
  • Angular.js FitText.js from @patrickmarabeas
  • AMP-HTML FitText
  • FitText UMD by @peacechen

Example of the first:

See the Pen Fitted Text with FitText (no jQuery) by Chris Coyier (@chriscoyier) on CodePen.


Swap the words in FitText around and you got yourself textFit! It’s another JavaScript library that adjusts font sizes to fit text into a container. Big caveat here though: textFit is designed for two-dimensions. So you need a width and height on the element for it to do it’s thing.

See the Pen Fitted Text with textFit by Chris Coyier (@chriscoyier) on CodePen.


fitty is more like FitText in that it resizes type to maximize just horizontally, but actually seems to require no magic numbers.

See the Pen Fitted Text with fitty by Chris Coyier (@chriscoyier) on CodePen.


TextFill is jQuery-based and requires a width, height, and a configured maximum font size to work. Here’s the basic demo we’ve been working from:

See the Pen Fitted Text with TextFill by Chris Coyier (@chriscoyier) on CodePen.


FlowType is kind of designed to work on a whole document of text, resizing it all fluidly at once, with minimum and maxium viewport sizes. But you can scope it however you want. You also apply a magic number to get things how you want them.

See the Pen Fitted Text with FlowType by Chris Coyier (@chriscoyier) on CodePen.

Just use SVG

With width: 100% and a viewBox, SVG will be a fullsize box that resizes with an aspect ratio. Pretty neat trick! To set the type, you’ll need some magic numbers to get that viewBox just right and push the text into the right spot — but it’s doable with zero dependencies, just like the viewport units demo.

See the Pen Fitted Text with SVG by Chris Coyier (@chriscoyier) on CodePen.

The post Fitting Text to a Container appeared first on CSS-Tricks.

How to create a logo that responds to its own aspect ratio

One of the cool things about <svg> is that it’s literally its own document, so @media queries in CSS inside the SVG are based on its viewport rather than the HTML document that likely contains it.

This unique feature has let people play around for years. Tim Kadlec experimented with SVG formats and which ones respect the media queries most reliably. Sara Soueidan experimented with that a bunch more. Jake Archibald embedded a canvas inside and tested cross-browser compatibility that way. Estelle Weyl used that ability to do responsive images before responsive images.

Another thing that has really tripped people’s triggers is using that local media query stuff to make responsive logos. Most famously Joe Harrison’s site, but Tyler Sticka, Jeremy Frank, and Chris Austin all had a go as well.

Nils Binder has the latest take. Nils take is especially clever in how it uses <symbol>s referencing other <symbol>s for extra efficiency and min-aspect-ratio media queries rather than magic number widths.

For the record, we still very much need container queries for HTML elements. I get that it’s hard, but the difficulty of implementation and usefulness are different things. I much prefer interesting modern solutions over trying to be talked out of it.

Direct Link to Article — Permalink

The post How to create a logo that responds to its own aspect ratio appeared first on CSS-Tricks.

Empower Through Web Development

As a person with a disability, I appreciate the web and modern-day computing for their many affordances. The web is a great place to work and share and connect. You can make a living, build your dream, and speak your mind.

It’s not easy, though. Beginners struggling with the box model often take to Google in search of guidance (and end up at this very website). More seasoned developers find themselves hopping from framework to framework trying to keep up. We experience plenty of late nights, console logs, and rage quits.

It’s in times like these that I like to remember why I’m doing this thing. And that’s because of the magic of creating. Making websites is empowering because it allows you to shape the world—in ways big and small, public and personal. It’s especially powerful for people with disabilities, who gain influence on the tech that they rely on. And, hey, you can do web stuff for fun and profit.

The magic of craft

I knew I wanted to be a person who makes websites the first time I ever wrote HTML, hit reload, and saw my changes. I felt the power coursing through my veins as I FTP-ed my site to my shiny new web server under my very own domain name. My mind jumped into the future, imagining my vast web empire.

I never got around to making an empire, but I had something most of my friends didn’t—a personal homepage. I don’t care how ugly or weird they might be, I think everyone should have a homepage they made for themselves. I love going to personal web homes—you know, just to check out what they’ve done with the place.

I love that the web can be this artisanal craft. My disability (spinal muscular atrophy) precludes me from practicing many crafts one might pick up. And yet as a child, I watched as my grandfather would take to his workshop and emerge with all sorts of wooden inventions—mostly toys for me, of course. I came to appreciate the focus and dedication he put into it. Regardless of what terrible things were going on in life, he could escape into this wonderful world of creation, systems, and problem-solving. I wanted that too. Years later, I found my craft. What with its quirky boxes, holy wars, and documentation.

But I loved it. I couldn’t get enough. And finally I realized… this can be my life’s work. I can get paid to make things on the internet.

But first, some not great facts about employment

Employment levels of people with disabilities are low, and those who are employed tend to be in low-paying occupations.

— U.S. Department of Labor

The U.S. Department of Labor (DOL) keeps statistics on employment levels for people with disabilities and, let me tell you, they kind of suck. The DOL goes on to say that only a third of working-age people with disabilities were employed, on average, in the 2010-2012 period. In contrast, over two-thirds of people without disabilities were employed in the same period.

This problem affected me personally as I struggled to get a job after college. Fortunately, the web is a great tool for breaking down barriers. With its vast learning resources, free and open source software, and plethora of ways to connect and share, the web makes possible all sorts of employment opportunities. I’m now a designer/developer at Mad Genius and loving it.

I urge anyone—disabled or not—who finds themselves with limited access to more conventional jobs to consider working on the web. Whether you draw, write, design, or code, there’s something here for you. This big web we’re building for everyone should be built by everyone.

We need that thing that you’re going to build

Last fall, I launched A Fine Start, a web service and browser extension for turning your new tab page into a minimal list of links. Some people saw it when Chris mentioned it in an article and it picked up quite a few new users. But what those users don’t know is that A Fine Start began life as an assistive technology.

Because of my extremely weak muscles, I type using a highly customized on-screen keyboard. It’s doable, but tedious, so I use the mouse method of getting things done wherever possible. I could open a new tab and start typing, but I would rather click. As a result, I made Start, a one-file tool that allowed me to save lists of links and get at them quickly when set as my new tab page—no keyboard necessary.

It was fantastic and I realized I needed Start on every computer I used, but I had no way of getting my bookmarks on another device without sticking them in a text file in Dropbox. So, last year, I wrote a backend service, polished the design, made a Chrome extension, and threw my baby out of the nest to see if it would fly. We’ll see.

The point is, there’s something the world needs, waiting to be built. And you are the only one who can build it. The recipe is mediocre ideas, showing up, and persistence. The web needs your perspective. Load up on your caffeinated beverage of choice, roll up your sleeves, and practice your craft. You’ve got the power. Now use it.

The post Empower Through Web Development appeared first on CSS-Tricks.

Free E-book: ​Modernize for Mobile Apps

(This is a sponsored post.)

No sign up required to read the free e-book.

Building modern apps (mobile, PWAs or Single Page Apps) that connect to legacy or enterprise systems is a pain. We put together an e-book that discusses the various options for how to make it all work. Here are some of the chapter contents:

  • The Challenges of Migrating to a Modern Mobile Architecture
  • Strategies for Migrating to Mobile
  • Strategies for Migrating to the Cloud
  • Data & Mobile Applications
  • Future-Proofing Your Modernization Efforts

Check out our Mobile Modernization: Architect Playbook

Direct Link to Article — Permalink

The post Free E-book: ​Modernize for Mobile Apps appeared first on CSS-Tricks.

Vue + TypeScript: A Match Made in Your Code Editor

Vue is so hot right now and I’ve been thinking of doing a serious project with it since quite a while, so when the opportunity popped up, I hopped in. But there was a little problem — one of the requirements of the project was to write it in TypeScript. At first, I was super stressed about how I was going to ever get started on this combo, but vue-cli made it so easy.

I’d be lying if I said this ride was super smooth. There were frustrations, hours of staring at the screen and some fistbumps with my table but after working with Vue + TypeScript for over a month now, I can say it was worth it — and if I had to code another app with Vue, I wouldn’t do it without TypeScript.


This article is about pairing Vue and TypeScript and assumes some basic knowledge of both. If you haven’t had a chance to play with them yet and are curious, Vue has a great guide, and the TypeScript docs are a great place to start.

We need to have vue-cli installed globally, so we can quickly spin up Vue project. Install vue-cli by running the following command in your terminal:

npm install -g @vue/cli

Once we have that installed, we’re good to go. If you don’t have TypeScript installed, we don’t need to do that beforehand, as vue-cli takes care of that when you start a new project and choose TypeScript there.

Getting Started

Now that we have vue-cli installed, all we need to do to get a project with Vue + TypeScript started is to run vue create. While creating a new project, choose TypeScript and we’re good to go.

vue create <app-name>

Here’s the result once our project spins up:

vue-cli also provides us the ability to choose Babel along with TypeScript for polyfills, CSS preprocessors, linter, unit testing libraries (I picked Jest, go Jest!) along with other config. You can even save your choices in a preset, to use it later, for another project.

Here’s a rundown of the handy questions you’ll get asked to configure the project:

One thing I’d like to mention is that vue-cli 3.0 comes with a user interface which makes it even more easy to create a new project. Run vue ui in terminal and vue-cli opens a vue-property-decorator package exposes Vue properties and makes them available to use as decorators. In my application, I ended up using only @Component, @Prop, @Watch but there are others such as @Emit, @Inject and @Model, that make your code much more verbose when used extensively.


Vuex has typings…nuff said! Vuex supports TypeScript to boot and, at first, I didn’t even know it. I started to look for proper ways to combine Vuex with TypeScript and stumbled upon Alex Jover Morales’ course on Vue.js State Management with Vuex and TypeScript. It helped me understand the correct way of managing Vuex state when using TypeScript.

For example:

// actions.ts
import { ActionTree } from 'vuex';
import { RootState, ModuleState } from '@/types'; const actions: ActionTree<ModuleState, RootState> = { // all your actions go here


This is yet another thing that I didn’t know existed when I first started but know wish I had found it sooner. I was creating getters for almost everything, but that didn’t feel right. I started looking for better ways to do this and found an interesting article by Francesco Vitullo which cleared up a few things for me. That’s where I found out about vuex-class which provides decorators for all vuex mappers.

So, now instead of writing a new getter for simply accessing a property in state, I could do this:

import { State,
} from 'vuex-class' @Component
export class MyComp extends Vue { @State(state => stateBar

Development Experience With VS Code

With TypeScript, the coding experience on VS Code is so much better. There is no going back and forth to check what mutation types I declared in mutation-types.ts because VS Code can recognize them and suggest correct ones as I type.

The same goes for modifying state in mutations — with TypeScript, the editor knew what my state structure looks like and suggests correct properties.

If you’re using VS Code, I strongly recommend using the Vetur plugin because it provides Vue tooling and comes with other bells and whistles like syntax highlighting (this works great with Vue single file components) and linting right out of the box.

Final Thoughts

Just like everything else in the JavaScript ecosystem, Vue + TypeScript still has a long way to go. For example, I could not use vuelidate because it doesn’t have typings. But thankfully vee-validate provided a workaround, so I didn’t have to go down the difficult road of writing those myself.

In conclusion, I found the development experience to be much smoother and VS Code is a totally different beast when you work with TypeScript. I don’t really need to sing the praises of Vue — it is very easy to pick up and start building and saves days trying to wrap your head around the inner workings of a framework.

The post Vue + TypeScript: A Match Made in Your Code Editor appeared first on CSS-Tricks.

Better rendering for variable fonts

I was messing around with a variable font the other day and noticed this weird rendering issue in the latest version of Chrome where certain parts of letterforms were clipping into each other in a really weird way. Thankfully, though, Stephen Nixon has come to the rescue with a temporary hack to fix the issue which using a text-shadow on the text that’s using the variable font:

.variable-font { text-shadow: 0 0 0 #000; /* text color goes last here */

Once you do that, you shouldn’t be able to see those weird clip marks in the letterforms anymore. Yeah, it feels pretty hacky but I’m sure this rendering bug will be fixed relatively soon. It doesn’t look like it affects other browsers, as far as I can tell.

Direct Link to Article — Permalink

The post Better rendering for variable fonts appeared first on CSS-Tricks.

Handling Errors with Error Boundary

Thinking and building in React involves approaching application design in chunks, or components. Each part of your application that performs an action can and should be treated as a component. In fact, React is component-based and, as Tomas Eglinkas recently wrote, we should leverage that concept and err on the side of splitting any large chunking into smaller components.

Splitting inevitably introduces component hierarchies, which are good because they bloated components and architecture. However, things can begin to get complicated when an error occurs in a child component. What happens when the whole application crashes?! Seriously, React, why do the parent and sibling components have to pay for the sins of another component? Why?

Error Boundaries

React 16 came with a lot of goodies, one of which is error boundaries. Let’s consult the documentation and break down what it says about this gem because we can use it to spot errors where they occur and resolve them faster and with less headache!

Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.

That’s a lot of jargon but, like components, we can break it down into less complex chunks.

Error boundaries are React Components

This makes a lot of sense and useful because it’s a concept we have using all along. The difference is that juice was sprayed on it to make it different from a normal component. Still, don’t forget the basic idea that error boundaries are themselves React Components!

Error boundaries catch JavaScript errors anywhere in their child component tree

In case you have forgotten how children component tree work, here is an example:

<ParentComponent> <FirstChild> <FirstChildDaughter> </FirstChildDaughter> </FirstChild> <SecondChild> </SecondChild>

We have two parent and three child components. According to what we have learned so far about error boundaries, we can replicate the above tree to:

<ErrorBoundaryComponent> <ParentComponent> <FirstChild> <FirstChildDaughter> </FirstChildDaughter> </FirstChild> <SecondChild> </SecondChild> </ParentComponent>

By wrapping the whole tree up in an ErrorBoundaryComponent, we can catch any JavaScript errors that occur in its child components. Cool, right?

Error boundaries log those errors

When errors are caught, we want boundaries errors to do something with them, preferably something to tell us about the. Developers often make use of error logging platforms to monitor errors that occur on their software. With error boundaries, we can do the same.

Error boundaries display a fallback UI

Instead of displaying the whole annoying combo of reds in different shades, you can choose a customized user interface to display when an error occurs. That can come in super handy because it allows you to tailor errors in a style that makes it easier for you to read and scan. Super cool, right?

Like me, you’ll think that this means error boundaries will catch all JavaScript errors. Sadly, that’s not true. Here are errors that they will gracefully ignore:

  • Event handlers
  • Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
  • Server-side rendering
  • Errors thrown in the error boundary itself (rather than its children)


The extra juice that makes a component an error boundary is componentDidCatch() — this is a lifecycle method that works like the JavaScript catch{} block, but for components. When an error is found in a child component, the error is handled by the closest error boundary. Only class components can be error boundaries.

componentDidCatch() accepts two parameters:

  • error: This is the error that was thrown
  • info: An object which contains a trace of where the error occurred

Error Boundary In Action

Say we are working on a feature that lists locations where conferences can be held. Something like this:

See the Pen error boundary 0 by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

The application lists locations from the Location component and the individual locations are output as Location Cards. We take a little extra care to ensure the name of each location is rendered in uppercase for consistency. For this tutorial purpose, we will add an empty object to the list of locations.

class Location extends React.Component { state = { locations: [ { "name": "Ojo", "zone": "Lagos State", "region": "South West" }, { "name": "Ahiazu Mbaise", "zone": "Imo State", "region": "South East" }, { "name": "Akoko-Edo", "zone": "Edo State", "region": "South South" }, { "name": "Anka", "zone": "Zamfara State", "region": "North West" }, { "name": "Akwanga", "zone": "Nasarawa State", "region": "North Central" }, { } ] } render() { return ( <div> <div> <div> <h2>Locations</h2> </div> </div> <div> {this.state.locations .map(location => <LocationCard key={} {...location} /> )} </div> </div> ) }
} const LocationCard = (props) => { return ( <div> <hr /> <p><b>Name:</b> {}</p> <p><b>Zone:</b> {}</p> <p><b>Region:</b> {props.region}</p> <hr /> </div> )
} const App = () => ( <div> <Location /> </div>
) ReactDOM.render(<App />, document.getElementById("root"));

If you run this in the browser, you will see an error similar to this screenshot:

A screenshot of the Type Error providing the error message Cannot read property toUpperCase of undefined. Background color is tan and there is a block of code with a light red background indicating where the error is in the code base.

That’s not totally helpful, so let’s apply an error boundary to handle help us out. First, we’ll create an ErrorBoundary component:

class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null, info: null }; } componentDidCatch(error, info) { this.setState({ hasError: true, error: error, info: info }); } render() { if (this.state.hasError) { return ( <div> <h1>Oops, something went wrong :(</h1> <p>The error: {this.state.error.toString()}</p> <p>Where it occured: {}</p> </div> ); } return this.props.children; }

An initial state for hasError, error, and info is created. Then, the componentDidCatch() lifecycle method is added. If an error occurs in the constructor, render or lifecycle method of any of its children components, the hasError state will be changed to true. When this happens, the ErrorBoundary component renders and displays the error. But if there are no errors, the children of the ErrorBoundary component are rendered instead as we’d expect.

Next, we need to add both the ErrorBoundary and Location components to our main App component:

const App = () => ( <div> <ErrorBoundary> <Location /> </ErrorBoundary> </div>

See the Pen error boundary 2 by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

We don’t see that annoying TypeError UI anymore! Things are working!

There’s one little thing we can do to improve the app. If you check the code in the demo, you’ll see an empty object we added at the end. Is it possible to have the other credible locations render? Most definitely! Inside the Location component, we can wrap the LocationCard component with the ErrorBoundary component to scope error logging directly to the cards:

class Location extends React.Component { state = { locations: [ { "name": "Ojo", "zone": "Lagos State", "region": "South West" }, { "name": "Ahiazu Mbaise", "zone": "Imo State", "region": "South East" }, { "name": "Akoko-Edo", "zone": "Edo State", "region": "South South" }, { "name": "Anka", "zone": "Zamfara State", "region": "North West" }, { "name": "Akwanga", "zone": "Nasarawa State", "region": "North Central" }, { // Empty! } ] } render() { return ( <div> <div> <div> <h2>Locations</h2> </div> </div> <div> {this.state.locations .map(location => <ErrorBoundary> // Should render all locations, but the empty instance <LocationCard key={} {...location} /> </ErrorBoundary> )} </div> </div> ) }

This time, the credible locations show, except the one that is empty. You can choose to wrap the whole component tree with an error boundary component once, or you can wrap different components at strategic places. The decision is up to you.

Wrapping Up

I encourage you to start making use of error boundaries in your applications. Similarly, it’s worth digging in a little deeper. For that, here are some issues in the React repo on Error Boundaries and event handles, go through them so you can see the current state of where things are at:

  • componentDidCatch is not getting called when there is an error in promise
  • Why are Error Boundaries not triggered for event handlers?

The post Handling Errors with Error Boundary appeared first on CSS-Tricks.

​Add real-time comments to a Gatsby blog

(This is a sponsored post.)

This tutorial will show you how to add realtime comments to a Gatsby blog. You will use Node and Express to create a simple backend, allowing users to add and view comments instantly.

Direct Link to Article — Permalink

The post ​Add real-time comments to a Gatsby blog appeared first on CSS-Tricks.

Resilient, Declarative, Contextual

Keith J. Grant:

I want to look at three key characteristics of CSS that set it apart from conventional programming languages: it’s resilient; it’s declarative; and it’s contextual. Understanding these aspects of the language, I think, is key to becoming proficient in CSS.

  1. Like HTML, unknown or slightly broken CSS doesn’t stop a site in its tracks.
  2. You write something you want to happen in CSS, it happens, and a bunch of related things may happen to. I like Keith’s example with font-size. Increase it, and the container will also grow in height without you having to tell it to.
  3. You can’t understand what CSS is going to do without understanding the DOM structure it is paired with and the other styles at play.

And it’s my suspicion that developers who embrace these things, and have fully internalized them, tend to be far more proficient in CSS.

Easy to learn, a lifetime to master, as they say.

Direct Link to Article — Permalink

The post Resilient, Declarative, Contextual appeared first on CSS-Tricks.

Drawing Images with CSS Gradients

What I mean by “CSS images” is images that are created using only HTML elements and CSS. They look as if they were SVGs drawn in Adobe Illustrator but they were made right in the browser. Some techniques I’ve seen used are tinkering with border radii, box shadows, and sometimes clip-path. You can find a lot of great examples if you search daily css images” on CodePen. I drew some myself, including this Infinity Gauntlet, but in one element with only backgrounds and minimal use of other properties.

Let’s take a look at how you can create CSS images that way yourself.

The Method

Understanding the shorthand background syntax as well as how CSS gradients work is practically all you need to draw anything in one element. As a review, the arguments are as follows:

background: <'background-color'> || <image> || <position> [ / <size> ]? || <repeat> || <attachment> || <origin> || <clip>;

They can occur in any order except that there must be a / between the position and size. We must keep those two arguments in that order as well, or else we’ll get unexpected results. Not all of these need to be included, and for this purpose we won’t be using the color, repeat, attachment, origin, or clip arguments. This leaves us with image, size, and position. Since backgrounds repeat by default, however, we must place background-repeat: no-repeat; right under everything in background (if certain backgrounds ought to be repeat, we can use repeating-linear-gradient() and repeating-radial-gradient()). In that case, the skeleton CSS will be this:

.image { background: <image> <position> / <size>; background-repeat: no-repeat;

We can even use multiple sets of background arguments! Therefore, we can stack and separate them with commas like this:

.image { background: <image> <position> / <size>, <image> <position> / <size>, <image> <position> / <size>; background-repeat: no-repeat;

The structure above is the basis of how we’ll draw images—one line per shape. Keep in mind that the rendering order is the opposite of how absolutely- or fixed-position elements are ordered. The first one will show up on top instead of at the bottom. In other words, the circles (radial gradients) below would be rendered from bottom to top (blue on bottom, red on top).

.circles { background: radial-gradient(7em 7em at 35% 35%, red 50%, transparent 50%), radial-gradient(7em 7em at 50% 50%, gold 50%, transparent 50%), radial-gradient(7em 7em at 65% 65%, blue 50%, transparent 50%); background-repeat: no-repeat; width: 240px; height: 240px;


We’ll use Sass (SCSS) to draw these images so we can make use of variables for a color palette. That will make the code shorter, easier to read and change darkened or lighter variants of colors. We could use variables in normal CSS instead and forget Sass, but due to Internet Explorer’s lack of support, let’s stick with Sass. To explain how this works, we’ll experiment with shapes using both linear and radial gradients.

Setting Up a Color Palette

Our palette will consist of RGB or HSL colors. I’ll explain later why to keep the colors in either of those formats. For this example, we’ll use RGB.

$r: rgb(255,0,0); // hsl(0,100%,50%)
$o: rgb(255,128,0); // hsl(32,100%,50%)

What I like to do to keep code short and easy to read is use a minimum of one letter to represent each color (e.g. $r for red). If using darker or lighter shades of one color, I add a d before the base letter or letters for dark or an l for light. I’d use $dr for dark red and $lr for light red. If there’s a need for more than two other shades, then I add a number at the end to indicate the shade level. For instance, $dr1 for dark red, $dr2 for a darker red, and $lr1 for light red, $lr2 for a lighter red. Such a palette would look like this (with dark first, normal next, and light last):

$dr1: rgb(224,0,0);
$dr2: rgb(192,0,0);
$r: rgb(255,0,0);
$lr1: rgb(255,48,48);
$lr2: rgb(255,92,92);

Setting the Scale and Canvas

We’ll use em units for the image dimensions so that the image can easily be resized proportionally. Since 1em is equal to the element’s font size, each unit of the image will be adjusted accordingly if changed. Let’s set a font size of 10px and set both the width and height to 24em. Units of 10px is the easiest to think in because if you mentally do the math, you instantly get 240 × 240px. Then just to see where the edges of the canvas are, we’ll use a 1px gray outline.

$r: rgb(255,0,0); // hsl(0,100%,50%)
$o: rgb(255,128,0); // hsl(32,100%,50%) .image { background-repeat: no-repeat; font-size: 10px; outline: 1px solid #aaa; width: 24em; height: 24em;

Be mindful about using smaller font sizes, however; browsers have a minimum font size setting (for accessiblity reasons). If you set a font size of 4px and the minimum is 6px, it’ll be forced at 6px.

Furthermore, you can enable responsiveness just by using calc() and viewport units. Perhaps we can use something like calc(10px + 2vmin) if desired, but let’s stick with 10px for now.

Drawing Shapes

The fun part begins here. To draw a square that is 8 × 8em in the center, we use a linear-gradient() with two same-colored stops.

.image { background: linear-gradient($r, $r) 50% 50% / 8em 8em; ...

To mold it into something more like a trapezoid, set an angle of 60deg. At the same time, let’s add $T for transparent to our palette and then place both the stops for $r and $T at 63% (right before the bottom-right corner gets cut off).

$T: transparent; .image { background: linear-gradient(60deg,$r 63%, $T 63%) 50% 50% / 8em 8em; ...

Setting both stops at the same value makes the slanted side as crisp as the others. If you look at it more closely though, it appears to be pixelated:

To correct this, we slightly adjust one of the stops (by 1% or roughly so) so that the edge is smooth enough. So, let’s change $r’s 63% to 62%.

This will be an issue with round edges as well while working with radial gradients, which we’ll see later. If you’re viewing this in a browser other than Safari, everything looks great even if transitioning to a non-transparent color instead (say orange). The problem with transitioning to transparent in Safari is that you’ll notice a bit of black lining at the slanted side.

This is because the transparent keyword in Safari is always black transparency, and we see some black as a result. I really wish Apple would fix this, but they never will. For the time being, let’s add a new $redT variable for red transparency under $r. Scrap the $T we used for transparent as we’ll no longer use it.

$rT: rgba(255,0,0,0); // hsla(0,100%,50%,0)

Then let’s replace transparent with $redT. This takes care of our Safari problem!

.image { background: linear-gradient(60deg,$r 62%, $rT 63%) 50% 50% / 8em 8em; ...

If you’ve been wondering why we weren’t using hex colors, Internet Explorer and Edge don’t support the #rgba and #rrggbbaa notations (yep, hex has had an alpha channel since late 2016 if you never knew), and we want this to work as cross-browser as possible. We also want to stay consistent with our choice of color format.

Now let’s move the shape vertically to 20% and draw an orange circle of the same dimensions. Also, add another variable for its transparent version. For the smooth edge, insert a 1% gap between the solid and transparent oranges.

$oT: rgba(255,128,0,0); // hsla(32,100%,50%,0) .image { background: linear-gradient(60deg,$r 62%, $rT 63%) 50% 20% / 8em 8em, radial-gradient(8em 8em at 50% 80%, $o 49%, $oT 50%); ...

To maintain consistency with our sizing, the second color stop should be at 50% instead of 100%.

Positioning Shapes

The way gradients are positioned is based on whether the unit is fixed or a percentage. Suppose we turn both of the gradients into squares and try to place them all the way across the div horizontally.

.image { background: linear-gradient($r, $r) 24em 20% / 8em 8em, linear-gradient($o, $o) 100% 80% / 8em 8em; ...

The red square ends up completely off canvas (outlined), and the right side of the orange square touches the other side. Using fixed units is like placing absolutely positioned elements or drawing shapes in HTML5 canvas. It’s true in that sense that the point of origin is at the top left. When using percent and a set background size, the div gets “fake padding” of half the background size. At the same time, the background’s point of origin is centered (not to be confused with background-origin, which regards box corners).

Now, if we turned these gradients into radial gradients as circles and applied the same x-positions 24em and 100%, both end up at the other side cut in half. This is because the point of origin is always in the center if we write the background like so:

.image { background: radial-gradient(8em 8em at 24em 20%, $r 49%, $rT 50%), radial-gradient(8em 8em at 100% 80%, $o 49%, $oT 50%); ...

If we rewrote the background so that the position and size are after the gradient and used 100% 100% at center, they’ll be positioned like the linear gradients. The red one ends up outside, and the orange one touches the right edge. The “fake padding” occurs once again with the orange.

.image { background: radial-gradient(100% 100% at center, $r 49%, $rT 50%) 24em 20% / 8em 8em, radial-gradient(100% 100% at center, $o 49%, $oT 50%) 100% 80% / 8em 8em; ...

There’s no single proper way to position shapes, but to place it like an absolutely or fixed positioned HTML element, use fixed units. If in need of a quick way to place a shape (using the position / size parameters) in the dead center, 50% is the best option as the shape’s origin will be its center. Use 100% if it should touch the very right side.

Sizing Shapes

Sizing in CSS backgrounds works as we’d expect, but it’s still influenced by the kind of unit used for position—fixed or percent. If we take our squares again and change their width to 10em, the red one expands to the right, and the orange one expands sideways.

.image { background: linear-gradient($r, $r) 12em 20% / 10em 8em, linear-gradient($o, $o) 50% 80% / 10em 8em; ...

If we used em units for the y-position, the shape will grow downwards or shrink upwards after changing height. If we use a percentage, then it will expand both vertical directions.

A moment ago, we looked at two ways to draw circles with radial gradients. The first way is to specify the width and height between the ( and at and then the position after that:

.image { background: radial-gradient(8em 8em at 50% 50%, $r 49%, $rT 50%); ...

The second way is to use 100% 100% in the center and then give the position and size:

.image { background: radial-gradient(100% 100% at 50% 50%, $r 49%, $rT 50%) 50% 50% / 8em 8em; ...

These methods both draw circles but will result in different outputs because:

  1. The first way occupies the whole div since there was no real background position or size.
  2. Giving a real position and size to the second sets it a bounding box. Consequently, it’ll behave just like a linear gradient shape.

Suppose we replaced $rT with $o. You’ll see that the orange will cover what was white or shapes under it (if we added any) for the first way. Using the second way, you’ll easily notice the bounding box revealed by the orange.

Additionally, the purpose of 100% 100% instead of using circle or ellipse is to allow the circle to occupy whole bounding box. It even gives us complete control over its dimensions. That way, it’ll remain the same if you change the 50% 50% position to something else. If using one of the two keywords, the edge of the circle stops only about 71% of the way when centered and becomes more distorted if its position is adjusted. For example, here’s what happens when we change the x-position to 0 for circle and ellipse:

In the long run, you can reimagine the syntax as radial-gradient(width height at x y) or radial-gradient(100% 100% at x-in-bounding-box y-in-bounding-box) x y / width height. If you are drawing just a circle or oval, you can simplify the code with the first way. If drawing part of a circle or part of a ring, then the second way comes into play. There will be many applications of that in the examples we’ll create next.


Ready to draw something for real now? We’ll walk through three examples step by step. The first two will be static—one with lots of half-circles and the other dealing with some rounded rectangles. The last example will be smaller but focused on animation.

Static Image

This parasol will be our first static image:

We’ll use a palette with red ($r and $rT), white ($w and $wT), orange ($o and $oT), and dark orange ($do and $doT).

$r: rgb(255,40,40);
$rT: rgba(255,40,40,0); $w: rgb(240,240,240);
$wT: rgba(240,240,240,0); $o: rgb(255,180,70);
$oT: rgba(255,180,70,0); $do: rgb(232,144,0);
$doT: rgba(232,144,0,0);

Let’s set up our drawing area of 30 × 29em.

.parasol { // background to go here background-repeat: no-repeat; font-size: 10px; outline: 1px solid #aaa; width: 30em; height: 29em;

Above the background-repeat, we’ll begin drawing the parts of the parasol. First, add the gradients that make up the pole (since neither overlap one another, the bottom-to-top order doesn’t matter at this point):

.parasol { background: // 1 radial-gradient(200% 200% at 100% 100%, $do 49%, $doT 50%) 14em 0 / 1em 1em, radial-gradient(200% 200% at 0% 100%, $o 49%, $oT 50%) 15em 0 / 1em 1em, // 2 linear-gradient(90deg, $do 50%, $o 50%) 14em 1em / 2em 25em, // 3 radial-gradient(100% 200% at 50% 0, $oT 0.95em, $o 1em, $o 1.95em, $do 2em, $do 2.95em, $doT 3em) 14em 26em / 6em 3em, // 4 radial-gradient(200% 200% at 100% 100%, $o 49%, $oT 50%) 18em 25em / 1em 1em, radial-gradient(200% 200% at 0% 100%, $do 49%, $doT 50%) 19em 25em / 1em 1em; ...
  1. To draw each side of the top of the pole, we used quarters of a circle that are 1 × 1em. To make them occupy their bounding boxes, we used circles that are twice the size (200% 200%) positioned at the bottom right and at the bottom left. We could also use keyword pairs like right bottom or left bottom, but it’s shorter to use the percent equivalents. Notice the 1% gaps between the stops to ensure smoothness.
  2. For the long part, we used a long rectangle with an abrupt dark orange-to-orange. There’s no need for a fractional tiny gap since we’re not working with a curve or slant.
  3. This part of the pole is a bit trickier to draw than the others because we have to maintain the 2em diameter. To draw this arc, we use a box of 6 × 3em so that there is a 2em space between the ends that are also 2em. Then we use a radial gradient at the center top where each stop occurs 1em each (and spread by 0.05em for smoothness).
  4. The last two are just like the first except they are positioned at the right end of the arc so that they seamlessly fit. Also, the colors swap places.

Then above the previous gradients, add the following from bottom to top to draw the top of the umbrella without the pointy ends:

.parasol { background: radial-gradient(100% 200% at 50% 100%, $r 50%, $rT 50.25%) 50% 1.5em / 9em 12em, radial-gradient(100% 200% at 50% 100%, $w 50%, $wT 50.25%) 50% 1.5em / 21em 12em, radial-gradient(100% 200% at 50% 100%, $r 50%, $rT 50.25%) 50% 1.5em / 30em 12em, ...

To draw the half circles that make up this part, we used a gradient size of 100% 200%, which makes each diameter fit into its background width but have twice the height and centered at the bottom. By ordering them bottom to top so that the largest is on bottom and smallest on top, we get the curves we want.

As our stack of gradients grows taller, it’ll become difficult after a while to identify which background or group of backgrounds corresponds to what part of the image. So to make it easier to pin them down, we can split them into groups lead by a comment describing what each group is for. Right now, we have split the stack to groups for the top of the parasol and the pole.

.parasol { background: /* top */ radial-gradient(100% 200% at 50% 100%, $r 50%, $rT 50.25%) 50% 1.5em / 9em 12em, radial-gradient(100% 200% at 50% 100%, $w 50%, $wT 50.25%) 50% 1.5em / 21em 12em, radial-gradient(100% 200% at 50% 100%, $r 50%, $rT 50.25%) 50% 1.5em / 30em 12em, /* pole */ radial-gradient(200% 200% at 100% 100%, $do 49%, $doT 50%) 14em 0 / 1em 1em, radial-gradient(200% 200% at 0% 100%, $o 49%, $oT 50%) 15em 0 / 1em 1em, linear-gradient(90deg, $do 50%, $o 50%) 14em 1em / 2em 25em, radial-gradient(100% 200% at 50% 0, $oT 0.95em, $o 1em, $o 1.95em, $do 2em, $do 2.95em, $doT 3em) 14em 26em / 6em 3em, radial-gradient(200% 200% at 100% 100%, $o 49%, $oT 50%) 18em 25em / 1em 1em, radial-gradient(200% 200% at 0% 100%, $do 49%, $doT 50%) 19em 25em / 1em 1em; ...

Then, in between the top and the pole, we’ll add the next chunk of backgrounds to render the pointy ends. To determine the widths of each segment, we must get the distance between each point where red and white meet. They all must add up to 30em.

Starting with the white and narrowest red half circles, we subtract the red’s width of 9em from the white’s width of 21em and divide the result by 2 to get the width of the two white segments (point “b” in the figure). So, the result would be 6em ( b = (21 – 9) / 2 = 6 ). Then the middle red segment would be 9em (21 – (6 + 6) = 9). What we have left now are the outer red segments (point “a” in the figure). Subtract the sum of what we have now from the width of the larger red half circle and divide that result by 2. That would be make the value of point a: (30em – (6 + 6 + 9)) / 2 = 4.5em.

.parasol { background: ... /* pointy ends */ radial-gradient() 0 13.5em / 4.5em 3em, radial-gradient() 4.5em 13.5em / 6em 3em, radial-gradient() 50% 13.5em / 9em 3em, radial-gradient() 19.5em 13.5em / 6em 3em, radial-gradient() 25.5em 13.5em / 4.5em 3em, ...

To draw the half circles similar to how we drew the top part, we start with the transparent counterpart of the color for each shape so that they resemble arc bridges. We’ll also add an extra 5% to each gradient width (not the background box width) so that each point formed by adjacent backgrounds won’t overly sharp and thin.

.parasol { background: ... /* pointy ends */ radial-gradient(105% 200% at 50% 100%, $rT 49%, $r 50%) 0 13.5em / 4.5em 3em, radial-gradient(105% 200% at 50% 100%, $wT 49%, $w 50%) 4.5em 13.5em / 6em 3em, radial-gradient(105% 200% at 50% 100%, $rT 49%, $r 50%) 50% 13.5em / 9em 3em, radial-gradient(105% 200% at 50% 100%, $wT 49%, $w 50%) 19.5em 13.5em / 6em 3em, radial-gradient(105% 200% at 50% 100%, $rT 49%, $r 50%) 25.5em 13.5em / 4.5em 3em, ...

Finally, you’ll no longer need that 1px solid #aaa outline. Our parasol is complete!

See the Pen Parasol by Jon Kantner (@jkantner) on CodePen.

Something With Rounded Rectangles

This next example will be an old iPhone model in which there are more details than the newer models. The thing about this one is the two rounded rectangles, which are the outside and middle of the home button.

The palette will consist of black ($bk and $bkT) for the home button edge, dark gray ($dg and$dgT) for the body, gray ($g and $gT) for the camera and speaker, light gray ($lg and $lgT) for the outside border, blue ($bl and $blT) for the camera lens, and a very dark purple ($p and $pT) for the screen.

$bk: rgb(10,10,10);
$bkT: rgba(10,10,10,0); $dg: rgb(50,50,50);
$dgT: rgba(50,50,50,0); $g: rgb(70,70,70);
$gT: rgba(70,70,70,0); $lg: rgb(120,120,120);
$lgT: rgba(120,120,120,0); $bl: rgb(20,20,120);
$blT: rgba(20,20,120,0); $p: rgb(25,20,25);
$pT: rgba(25,20,25,0);

Let’s set up our canvas at 20 × 40em and use the same font size we used for the parasol, 10px:

.iphone { // background goes here background-repeat: no-repeat; font-size: 10px; outline: 1px solid #aaa; width: 20em; height: 40em;

Before we begin drawing our first rounded rectangle, we need to think about our border radius, which will be 2em. Also, we want to leave some space at the left for the lock switch and volume buttons, which will be 0.25em. For this reason, the rectangle will be 19.75 × 40em. Considering the 2em border radius, we’ll need two linear gradients intersecting each other. One must have a width of 15.75em (19.75em – 2 × 2), and the other must have a height of 36em (40em – 2 × 2). Position the first 2.25em from the left and then the second 0.25em from the left and 2em from the top.

.iphone { background: /* body */ linear-gradient() 2.25em 0 / 15.75em 40em, linear-gradient() 0.25em 2em / 19.75em 36em; ...

Since the light gray border will be 0.5em thick, let’s make each gradient stop immediately switch from light gray ($lg) to dark gray ($dg) and vice versa at 0.5em and 0.5em before the end (40em – 0.5 = 39.5em for the first gradient, 19.75em – 0.5 = 19.25em for the second). Set an angle of 90deg for the second to make it go horizontal.

.iphone { background: /* body */ linear-gradient($lg 0.5em, $dg 0.5em, $dg 39.5em, $lg 39.5em) 2.25em 0 / 15.75em 40em, linear-gradient(90deg, $lg 0.5em, $dg 0.5em, $dg 19.25em, $lg 19.25em) 0.25em 2em / 19.75em 36em; ...

In each square corner, as indicated by the orange box in the figure, we’ll place the rounded edges. To create those shapes, we use radial gradients that are twice the size of their bounding boxes and located in each corner. Insert them above the body backgrounds.

.iphone { background: /* corners */ radial-gradient(200% 200% at 100% 100%, $dg 1.45em, $lg 1.5em, $lg 50%, $lgT 51%) 0.25em 0 / 2em 2em, radial-gradient(200% 200% at 0% 100%, $dg 1.45em, $lg 1.5em, $lg 50%, $lgT 51%) 18em 0 / 2em 2em, radial-gradient(200% 200% at 100% 0%, $dg 1.45em, $lg 1.5em, $lg 50%, $lgT 51%) 0.25em 38em / 2em 2em, radial-gradient(200% 200% at 0% 0%, $dg 1.45em, $lg 1.5em, $lg 50%, $lgT 51%) 18em 38em / 2em 2em, ...

To get the 0.5em-thick light gray ends, think about where the gradient starts and then do the math. Because the light gray is at the end, we subtract 0.5em from 2em to properly place first stop. For the smoothness, we take off a tiny bit from the first 1.5em and add 1% to the second 50% so that the round edges blend in with the flat edges.

Now if we enlarged the image by changing the font size to 40px or more, we notice seams between the rounded and flat edges (circled in orange):

Since they appear to be so tiny, we can easily patch them by going back to the body backgrounds and slightly altering the gradient stops as long as everything still looks right when changing the font size back to 10px.

.iphone { background: /* body */ linear-gradient($lg 0.5em, $dg 0.55em, $dg 39.5em, $lg 39.55em) 2.25em 0 / 15.75em 40em, linear-gradient(90deg, $lg 0.5em, $dg 0.55em, $dg 19.175em, $lg 19.25em) 0.25em 2em / 19.75em 36em; ...

Then in one linear gradient, we’ll add the lock switch and volume buttons to fill the 0.25em space on the left. If a 1px space is going to happen between the buttons and body, we can add a tiny bleed of 0.05em to the background width (making it 0.3em) so that it won’t stick out into the dark gray.

.iphone { background: /* volume buttons */ linear-gradient($lgT 5em, $lg 5em, $lg 7.5em, $lgT 7.5em, $lgT 9.5em, $lg 9.5em, $lg 11em, $lgT 11em, $lgT 13em, $lg 13em, $lg 14.5em, $lgT 14.5em) 0 0 / 0.3em 100%, ...

It looks like we could’ve used three light gray-to-light gray gradients, but since it was possible, only one was needed. It’s just lots of sudden transitions between the transparent and opaque light grays running downwards.

Next, let’s add the edge of the home button as well as the flat edges of the square inside it. Now the square inside home button will be 1.5 × 1.5em and follow basically the same procedure as the body: two intersecting linear gradients and radials to fill the corners. To place them horizontally in the center, calc() comes in handy. 50% + 0.125em will be the expression; if we centered only the phone body, there will be 0.125em spaces on each side. Therefore, we move it 0.125em more to the right. The same x-positioning will apply to the upper two backgrounds.

.iphone { background: /* home button */ linear-gradient() calc(50% + 0.125em) 36.5em / 0.5em 1.5em, linear-gradient() calc(50% + 0.125em) 37em / 1.5em 0.5em, radial-gradient(3em 3em at calc(50% + 0.125em) 37.25em, $bkT 1.25em, $bk 1.3em, $bk 49%, $bkT 50%), ...

Similar to how we shaded the linear gradient parts of the phone body, the stops will begin and end with light gray but with transparent in the middle. Notice we left 0.05em gaps between each gray-to-transparent transition (and vice versa). Just like the corners of the body, this is to ensure the blend between a round corner and flat end inside.

.iphone { background: /* home button */ linear-gradient($lg 0.15em, $lgT 0.2em, $lgT 1.35em, $lg 1.35em) calc(50% + 0.125em) 36.5em / 0.5em 1.5em, linear-gradient(90deg, $lg 0.15em, $lgT 0.2em, $lgT 1.3em, $lg 1.35em) calc(50% + 0.125em) 37em / 1.5em 0.5em, radial-gradient(3em 3em at calc(50% + 0.125em) 37.25em, $bkT 1.25em, $bk 1.3em, $bk 49%, $bkT 50%), ...

By the way, because the outlines will be so small as you’ve seen earlier, we can better see what we’re doing by increasing the font size to at least 20px. It’s like using the zoom tool in image editing software.

Now to get the corners of the gray square to exactly where they should be, let’s focus on the x-position first. We start with calc(50% + 0.125em), then we add or subtract the width of each piece, or should I say the square’s border radius. These backgrounds will go above the last three.

.iphone { background: /* home button */ radial-gradient(200% 200% at 100% 100%, $lgT 0.3em, $lg 0.35em, $lg 0.48em, $lgT 0.5em) calc(50% + 0.125em - 0.5em) 36.5em / 0.5em 0.5em, radial-gradient(200% 200% at 0% 100%, $lgT 0.3em, $lg 0.35em, $lg 0.48em, $lgT 0.5em) calc(50% + 0.125em + 0.5em) 36.5em / 0.5em 0.5em, radial-gradient(200% 200% at 100% 0%, $lgT 0.3em, $lg 0.35em, $lg 0.48em, $lgT 0.5em) calc(50% + 0.125em - 0.5em) 37.5em / 0.5em 0.5em, radial-gradient(200% 200% at 0% 0%, $lgT 0.3em, $lg 0.35em, $lg 0.48em, $lgT 0.5em) calc(50% + 0.125em + 0.5em) 37.5em / 0.5em 0.5em, ...

Then the screen will be a 17.25 × 30em rectangle. Just like parts of the home button, we’ll horizontally center it using calc(50% + 0.125em). From the top, it’ll be 5em.

.iphone { background: /* screen */ linear-gradient($p, $p) calc(50% + 0.125em) 5em / 17.25em 30em, ...

Lastly, we’ll add the camera and speaker. The camera is a straightforward 1 × 1 blue-to-gray radial with no fancy calculations. The pure-gray speaker though will be a bit more involved. It will be a 5 × 1em rectangle and have a 0.5em border radius. To draw that, we first draw a rectangle with the first 4ems of the width and center it with calc(50% + 0.125em). Then we use 0.5 × 1em half circles in which the gradient positions are 100% 50% and 0% 50%. The best way to position these left and right of the rectangle is to use some new calc() expressions. For the left, we’ll subtract half the rectangle width and half the half circle width from the body center (50% + 0.125em - 2em - 0.25em). The right will follow the same pattern but with addition, so 50% + 0.125em + 2em + 0.25em.

.iphone { background: /* camera */ radial-gradient(1em 1em at 6.25em 2.5em, $bl 0.2em, $g 0.21em, $g 49%, $gT 50%), /* speaker */ radial-gradient(200% 100% at 100% 50%, $g 49%, $gT 50%) calc(50% + 0.125em - 2em - 0.25em) 2em / 0.5em 1em, radial-gradient(200% 100% at 0% 50%, $g 49%, $gT 50%) calc(50% + 0.125em + 2em + 0.25em) 2em / 0.5em 1em, linear-gradient($g, $g) calc(50% + 0.125em) 2em / 4em 1em, ...

Remove the gray outline around the div, and the iPhone is complete!

See the Pen iPhone by Jon Kantner (@jkantner) on CodePen.

Animated Images

You might be thinking you could use background-position to animate these sorts of images, but you can only do so much. For instance, it’s impossible to animate the rotation of an individual background by itself. In fact, background-position animations don’t typically perform as well as transform animations, so I don’t recommend it.

To animate any part of an image any way we wish, we can let the :before or :after pseudo-elements be responsible for that part. If we need more selections, then we can revert to multiple child divs, yet not needing one for each little detail. For our animated image example, we’ll create this animated radar:

We draw the static part first, which is everything except the gray frame and rotating hand. Before that, let’s supply our palette (note: We won’t need a $dgnT for $dgn) and base code.

$gn: rgb(0,192,0);
$gnT: rgba(0,192,0,0);
$dgn: rgb(0,48,0);
$gy: rgb(128,128,128);
$gyT: rgba(128,128,128,0);
$bk: rgb(0,0,0);
$bkT: rgba(0,0,0,0); .radar { background-repeat: no-repeat; font-size: 10px; outline: 1px solid #aaa; width: 20em; height: 20em;

Since this image is going to be completely round, we can safely apply a border radius of 50%. Then, we can use a repeating radial gradient to draw the rings—about 1/3 way apart from each other.

.radar { background: /* rings */ repeating-radial-gradient($dgn, $dgn 2.96em, $gn 3em, $gn 3.26em, $dgn 3.3em); background-repeat: no-repeat; border-radius: 50%; ...

Also note the extra $dgn at the start. For repeating gradients to start, end, and loop as expected, we need to specify the starting color at 0 (or without 0).

Unlike the previous example, we’re not using calc() to center the lines because Internet Explorer will render the whole thing awkwardly when we use a pseudo-element later. To draw four 0.4em lines that intersect one other in the center, know that half of the line should be half the div at 10em. So then, we subtract and add half of 0.4 (0.4 / 2 = 0.2) on each side. In other words, the left of the green should be 9.8em, and the right should be 10.2em. For the 45deg diagonals though, we must multiply 10em by the square root of 2 to get their center (10 × √2 ≈ 14.14). It’s the length of the longest side of a 10em right triangle. As a result, the sides of each diagonal would be approximately at 13.94 and 14.34em.

.radar { background: /* lines */ linear-gradient($gnT 9.8em, $gn 9.8em, $gn 10.2em, $gnT 10.2em), linear-gradient(45deg,$gnT 13.94em, $gn 13.98em, $gn 14.3em, $gnT 14.34em), linear-gradient(90deg,$gnT 9.8em, $gn 9.8em, $gn 10.2em, $gnT 10.2em), linear-gradient(-45deg,$gnT 13.94em, $gn 13.98em, $gn 14.3em, $gnT 14.34em), ...

To prevent the pixelation of the diagonals, we left a tiny 0.04em gap between green and transparent green. Then, to create some lighting, add this transparent-to-black radial gradient:

.radar { background: /* lighting */ radial-gradient(100% 100%, $bkT, $bk 9.9em,$bkT 10em), ...

That completes the static part of the radar. Now we draw the gray frame and hand in another background stack under :before and add the animation. There’s a reason we didn’t include the frame here. Because the hand container should fit the whole div, we don’t want it to overlap the frame.

This pseudo-element shall fill the space, and to ensure it stays in there, let’s absolutely position it. We’ll use the same border radius so that it stays round while animated in Safari.

.radar { ... position: relative; &:before { background-repeat: no-repeat; border-radius: 50%; content: ""; position: absolute; width: 100%; height: 100%; }

Then, to draw the hand, we make it half the size of its container and keep it at the top left corner. Finally, on top of that, we draw the frame.

.radar { ... &:before { animation: scan 5s linear infinite; background: /* frame */ radial-gradient($gyT 9.20em, $gy 9.25em, $gy 10em, $gyT 10.05em), /* hand */ linear-gradient(45deg, $gnT 6em, $gn) 0 0 / 50% 50%; ... }
} @keyframes scan { from { transform: rotate(0); } to { transform: rotate(1turn); }

Now our little gadget is complete!

See the Pen Radar by Jon Kantner (@jkantner) on CodePen.

Benefits (Plus a Drawback)

This approach of drawing CSS images has several advantages. First, the HTML will be very lightweight compared to a rasterized image file. Second, it’s great for tackling images that are impossible to draw well without using experimental properties and APIs that might not be widely supported.

It’s not to say that this method is better than using a parent element nested with children for the shapes. There is a drawback though. You have to give up being able to highlight individual shapes using the browser dev tools. You’ll need to comment and uncomment a background to identify which it is. As long as you group and label each chunk of backgrounds, you can find that particular background faster.


In a nutshell, the method for drawing of CSS images we’ve covered in this post allows us to:

  1. Set up a palette made up of variables for the colors.
  2. Disable the background repeat, set a scale with font-size, and a canvas width and height in em units for the target element.
  3. Use a temporary outline to show the edges as we work.
  4. Draw each shape from bottom to top because backgrounds are rendered in that order. The background syntax for each shape follows image position / size (with or without the position and size).

There’s a lot of thinking outside the box going on as well as experimentation to get the desired result. The three examples we created were just enough to demonstrate the concept. We’ve looked at how we order each background, drawing parts of circles, rounded rectangles, and slightly adjusting gradient stops for smooth edges. To learn more, feel free to dissect and study other examples I’ve made in this CodePen collection!

The post Drawing Images with CSS Gradients appeared first on CSS-Tricks.