I’m a huge fan of CSS Grid and I use it on pretty much every project these days. However, there’s one part of it that makes things much more complicated than they really ought to be: the lack of subgrids. And in this post on the matter, Ken Bellows explains why they’d be so gosh darn useful:
But one thing still missing from the Level 1 spec is the ability to create a subgrid, a grid-item with its own grid that aligns in one or both dimensions with the parent grid. It was originally planned to be in Level 1, but the working group decided they needed more time to work out the details, so it was removed, and it will ship in CSS Grid Layout Module Level 2, which seems to be nearing completion.
There has been a lot of discussion over the last 2 years about the use cases for subgrid, how it should be implemented, and even some debate over whether you even need it. A lot of that discussion was centered around two other approaches that can handle many of the same problems as subgrid: nested grids and display: contents
I remember one of the very first websites I worked on was much like the demo that Ken uses as an example, but this was way back in 2012 and grid didn’t exist yet. Sadly, I had to write a lot more CSS than I felt was necessary to get elements in one div to line up with elements in another. Anyway, this article kinda riffs off of Rachel Andrew’s post about subgrid and what problems it would help solve which is definitely worth checking out, too.
GitHub has made its first really big change since Microsoft acquired it a while back, and it’s a good one for small-to-medium sized design studios, freelancers, and anyone who writes front-end code for fun and profit: Free users now get unlimited private repositories. For free. No money.
GitHub has always offered free public repositories, but you had to pay for the private ones; a move which encouraged the flourishing of open source software. But I have personally avoided using it on more than one occasion for two reasons:
Who really wants people to see their code before it’s ready? That can be kind of embarrassing, especially if your the kind of person who works messy, and cleans everything up after.
Sometimes you’ll be working under an NDA, or even just a handshake agreement to keep things quiet until they’re ready. Public repos kind of defeat that purpose.
Now instead of heading on over to a competitor like BitBucket, GitHub is an option for people who don’t want everyone to see all those missing semicolons and incredibly vague code comments. Well, it’s an option so long as no more than three people will be working on it. So… freelancers and studios with small dev teams, basically.
If you’ve been avoiding GitHub for any of these reasons, now is the time to get some GitHub experience. It’s nearly essential to working on many teams these days, so it’s good to get in some practice. Besides, GitHub’s GUI for Windows is really good right now, so long as you understand the basic concepts behind Git and its version control.
So that’s what you get out of it. What does Microsoft get out of doing something so nice for all of us working on our personal projects? Well:
Microsoft gets good press. (You’re reading some of it right now.)
Microsoft encourages more people to work and play with GitHub, meaning people will be more inclined to adopt the platform when they’re starting their own enterprise-level projects, or when they’re part of a bigger team.
Really, they didn’t have much to lose. Enterprise clients typically need more than three developers to work on a project at a time, so it hasn’t cut into their business model. A few extra projects on their already massive servers are worth more potential customers in the long run.
When I first wrote about Microsoft’s acquisition of GitHub, I believed Microsoft would see the business opportunities in not completely destroying GitHub and its community. So far, so good. Their treatment of GitHub and Visual Studio Code give me hope. Now if only they’ll stop messing about with Skype and Windows 10 updates, they could really impress us.
In all of the excitement about CSS Grid Layout and Flexbox, another layout method is often overlooked. In this article I’m going to take a look at Multi-column Layout — often referred to as multicol or sometimes “CSS Columns”. You’ll find out which tasks it is suited for, and some of the things to watch out for when making columns.
What Is Multicol?
The basic idea of multicol, is that you can take a chunk of content and flow it into multiple columns, as in a newspaper. You do this by using one of two properties. The column-count property specifies the number of columns that you would like the content to break into. The column-width property specifies the ideal width, leaving the browser to figure out how many columns will fit.
It doesn’t matter which elements are inside the content that you turn into a multicol container, everything remains in normal flow, but broken into columns. This makes multicol unlike other layout methods that we have in browsers today. Flexbox and Grid for example, take the child elements of the container and those items then participate in a flex or grid layout. With multicol, you still have normal flow, except inside a column.
In the below example I am using column-width, to display columns of at least 14em. Multicol assigns as many 14em columns as will fit and then shares out the remaining space between the columns. Columns will be at least 14em, unless we can only display one column in which case it may be smaller. Multicol was the first time that we saw this kind of behavior in CSS, columns being created which were essentialy responsive by default. You do not need to add Media Queries and change the number of columns for various breakpoints, instead we specify an optimal width and the browser will work it out.
The column boxes created when you use one of the column properties can’t be targeted. You can’t address them with JavaScript, nor can you style an individual box to give it a background color or adjust the padding and margins. All of the column boxes will be the same size. The only thing you can do is add a rule between columns, using the column-rule property, which acts like border. You can also control the gap between columns using the column-gap property, which has a default value of 1em however you can change it to any valid length unit.
That is the basic functionality of multicol. You can take a chunk of content and split it into columns. Content will fill the columns in turn, creating columns in the inline direction. You can control the gaps between columns and add a rule, with the same possible values as border. So far so good, and all of the above is very well supported in browsers and has been for a long time, making this spec very safe to use in terms of backwards compatibility.
There are some further things you might want to consider with your columns, and some potential issues to be aware of when using columns on the web.
Spanning Columns
Sometimes you might like to break some content into columns, but then cause one element to span across the column boxes. Applying the column-span property to a descendent of the multicol container achieves this.
In the example below, I have caused a
element to span across my columns. Note that when you do this, the content breaks into a set of boxes above the span, then starts a new set of column boxes below. The content doesn’t jump across the spanned element.
The column-span property is currently being implemented in Firefox and is behind a feature flag.
Be aware that in the current spec, the values for column-span are either all or none. You can’t span just some of the columns, but you can get the kind of layout you might see in a newspaper by combining multicol with other layout methods. In this next example, I have a grid container with two column tracks. The left-hand track is 2fr, the right-hand track 1fr. The article in the left-hand track I have turned into a multicol container with two tracks, it also has a spanning element.
On the right, we have an aside which goes into the second Grid column track. By playing around with the various layout methods available to us, we can figure out exactly which layout method suits the job at hand — don’t be afraid to mix and match!
If you have content containing headings, then you probably want to avoid the situation where a heading ends up as the last thing in a column with the content going into the next column. If you have images with captions then the ideal situation would be for the image and caption to stay as one unit, not becoming split across columns. To deal with these problems CSS has properties to control where the content breaks.
When you split your content into columns, you perform what is known as fragmentation. The same is true if you split your content between pages, such as when you create a stylesheet for a print context. Therefore, multicol is closest to Paged Media than it is to other layout methods on the web. Because of this, for several years the way to control breaks in the content has been to use the page-break- properties which were part of CSS2.1.
page-break-before
page-break-after
page-break-inside
More recently the CSS Fragmentation specification has defined properties for fragmentation which are designed for any fragmented context, the spec includes details for Paged Media, multicol and the stalled Regions spec; Regions also fragments a continuous piece of content. By making these properties generic they can apply to any future fragmented context to, in the same way that the alignment properties from Flexbox were moved into the Box Alignment spec in order that they could be used in Grid and Block layout.
break-before
break-after
break-inside
As an example, I have used break-inside avoid on the
Oftentimes, no matter how you handle a project, the client is an *** and you’ve no choice but to walk away. JW Waste Management [not its real name] is that kind of client.
If we’d paid attention to our collective gut, spotted the obvious warning signs, then we wouldn’t have been caught out. Hopefully some of the lessons we learned will help you avoid the same problems.
The Kickoff Meeting
JW Waste Management is a trash disposal business, they clean out derelict properties, remove trash from waste ground, collect and dispose of small-scale commercial waste. They had (and still have) a site that in our opinion left them worse off than no site at all.
We met with the partners, William and Jim [not their real names] on a muddy lot they sublease from Jim’s father. We arrived 5 minutes early, and spent two hours waiting for them to turn up.
…he doesn’t believe in websites…
When they finally pulled up in a shiny new garbage truck, William climbed out of the cab to greet us but Jim walked away without a second glance. “Don’t mind him,” said William, “he doesn’t believe in websites.”
That should have been our first warning.
William claimed that he and Jim were equal partners, which we later found to not be the case. Best friends in their mid-thirties, they’d met at the local boxing gym and agreed to go into business together. As we later discovered, Jim was already in the family business, William was joining him.
The Brief
The brief from William was simple: Fix the serious bugs in their current site as a short-term patch, and then build them a site that would attract new business.
The brief from Jim was a little blunter, half way through the kickoff meeting he reappeared. “Look bro,” he said, “I don’t give a **** what you do, you just better get me some good contracts or God help you.”
That should have been our second warning, but we laughed it off. Jim had invested in an $800k truck for hauling larger loads and he was obviously just scared he couldn’t pay for it.
The Research Phase
We spent the day doing the rounds with William, learning how their business operated. We rode round in the truck as William picked up trash from various construction projects.
…if we stuck to the letter of the law we wouldn’t make enough money…
Some bins were dropped off at a recycling center, others were dumped out on a concealed patch of scrubland next to their lot. My partner asked if it was legal. “No,” was the short reply, “we’re not zoned for it, but if we stuck to the letter of the law we wouldn’t make enough money.”
We should have walked away there and then. I don’t know why we didn’t. It should have been strike three.
Once we understood how their business operated, we looked into the competition, and this is where the trouble really began.
12 miles away was a rival business called Hudson’s [not its real name]. Hudson’s was an established business that controlled most of the waste disposal in the area. William and Jim modelled themselves on Hudson’s, and wanted to copy the business model. It later turned out they wanted to copy the website too.
Version 1.0
At first, it was a good project to work on. Having patched up their existing site, we began work on a fresh approach.
Waste disposal is not a particularly interesting topic, but we came up with a design that addressed not the waste removal, but the results—clean properties, clean land, buildings ready to be reoccupied. It was a good campaign.
We pitched the idea, then heard nothing back. For weeks. Follow up emails and calls were ignored. Finally Jim accidentally copied us into an email to William: “I’m not paying these *******, who told them they were hired anyway, this is ********, it’s not what I want, no way.”
We were stunned.
We phoned William, when he didn’t answer we rang again, and again. On the third time he picked up. William couldn’t have grovelled harder: Jim didn’t mean to speak that way, he’s not used to business people, they absolutely did want to work with us, they had both signed the contract, Jim was just letting off steam.
We would later realise that William was very afraid of Jim. Their relationship verged on abusive. But at the time, we allowed William to reassure us.
William did have one suggestion: When we’d talked through the brief, they had really liked Hudson’s’ website, could we make our design a little more like that. “Sure,” we said, “we’ll see what we can do.”
Version 2.0
We’re a young agency, and I figure when you’re starting out, you feel like it’s your responsibility to be professional and to make things work. That’s what we tried to do.
As we designed version 2.0 we kept in regular contact with William, a phone call every other day. On every call William told us how much he liked our original idea, but that Jim would never go for it. We tried to reassure him that everything was fine, we’d moved on, we just wanted to do a good job for them.
This time, instead of pitching a complete design, we pitched in stages, trying to get each element signed-off before moving on. Each time, the feedback was the same: “It’s awesome, but it’s missing something. How do Hudson’s do it?”
They’ll look completely different once you’ve applied your ‘design magic’.
As the design came closer to completion we insisted on a face to face to discuss the design. We presented the work alongside a screenshot of Hudson’s’ site; the two sites looked identical.
We tried to explain that it was unethical, and poor business practice to plagiarize another site. “No problem,” said William, “They’ll look completely different once you’ve applied your ‘design magic’.”
We left wondering what they thought our ‘design magic’ was.
The Final Straw
Plagiarized or not, we were well over schedule, and set to make a loss on the job, so sign-off secured we passed the mockups over to our developer.
As the job was coming to completion, we asked William for the text and images. Instead, we received a message from Jim. They had been discussing the design and had decided that the design should be altered to only use their brand blue, with no accent colors at all. After we complained that it simply wouldn’t work, Jim agreed to allow us to add some chrome. When we explained that chrome wouldn’t work on screen he hung up.
With a completed build, but no content, we asked William and Jim to supply the content we needed.
After several false starts, William asked if he could send us some images that we could fix up, and add their logo where necessary. We said we would try. An hour later a link to an image archive arrived.
…we couldn’t believe what we were seeing…
Uncompressing the folder we couldn’t believe what we were seeing. The images had all been stolen from Hudson’s’ site.
My partner was ready to walk, but I wanted to try and salvage something so I called William. “You can just change the color to our blue, and add our logo,” he said, “no one will know.” Despite my protests, he couldn’t see the problem.
That was where we drew the line. I said what we should have said months earlier: No.
The End
We believed that by being professional, polite, and working hard, we could turn the project around. But we should have listened to our guts. We thought we could drag them up, but they dragged us down. We crossed several lines we didn’t want to cross, in the name of being professional.
As soon as we realized that William and Jim wanted a carbon copy of their nearest rival’s site, we should have walked away.
The moral of the story is, don’t work for people who don’t respect you, and definitely don’t work for people who don’t respect themselves.
A challenge I faced in building an image “emojifier” was that I needed to change the color spaces of values obtained using getImageData() from RGB to HSL. I used arrays of emojis arranged by brightness and saturation, and they were HSL-based for the best matches of average pixel colors with the emojis.
In this article, we’ll study functions that will be useful for converting both opaque and alpha-enabled color values. Modern browsers currently support the color spaces RGB(A), hex, and HSL(A). The functions and notations for these are rgb(), rgba(), #rgb/#rrggbb, #rgba/#rrggbbaa, hsl(), and hsla(). Browsers have always supported built-in names like aliceblue as well.
Along the way, we’ll encounter use of some color syntaxes provided by a new Level 4 of the CSS Colors Module. For example, we now have hex with alpha as we mentioned (#rgba/#rrggbbaa) and RGB and HSL syntaxes no longer require commas (values like rgb(255 0 0) and hsl(240 100% 50%) became legal!).
Browser support for CSS Colors Level 4 isn’t universal as of this writing, so don’t expect new color syntaxes to work in Microsoft browsers or Safari if trying them in CSS.
RGB to Hex
Converting RGB to hex is merely a change of radices. We convert the red, green, and blue values from decimal to hexadecimal using toString(16). After prepending 0s to single digits and under, we can concatenate them and # to a single return statement.
function RGBToHex(r,g,b) {
r = r.toString(16);
g = g.toString(16);
b = b.toString(16);
if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;
return "#" + r + g + b;
}
RGB in String
Alternatively, we can use a single string argument with the red, green and blue separated by commas or spaces (e.g. "rgb(255,25,2)", "rgb(255 25 2)"). Substring to eliminate rgb(, split what’s left by the ), then split that result’s first item by whichever the separator (sep) is. r, g, and b shall become local variables now. Then we use + before the split strings to convert them back to numbers before obtaining the hex values.
function RGBToHex(rgb) {
// Choose correct separator
let sep = rgb.indexOf(",") > -1 ? "," : " ";
// Turn "rgb(r,g,b)" into [r,g,b]
rgb = rgb.substr(4).split(")")[0].split(sep);
let r = (+rgb[0]).toString(16),
g = (+rgb[1]).toString(16),
b = (+rgb[2]).toString(16);
if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;
return "#" + r + g + b;
}
In addition, we can allow strings with channel values as percentages by adding the loop after redefining rgb. It’ll strip the %s and turn what’s left into values out of 255.
function RGBToHex(rgb) {
let sep = rgb.indexOf(",") > -1 ? "," : " ";
rgb = rgb.substr(4).split(")")[0].split(sep);
// Convert %s to 0–255
for (let R in rgb) {
let r = rgb[R];
if (r.indexOf("%") > -1)
rgb[R] = Math.round(r.substr(0,r.length - 1) / 100 * 255);
/* Example:
75% -> 191
75/100 = 0.75, * 255 = 191.25 -> 191
*/
}
...
}
Now we can supply values like either of these:
rgb(255,25,2)
rgb(255 25 2)
rgb(50%,30%,10%)
rgb(50% 30% 10%)
RGBA to Hex (#rrggbbaa)
Converting RGBA to hex with the #rgba or #rrggbbaa notation follows virtually the same process as the opaque counterpart. Since the alpha (a) is normally a value between 0 and 1, we need to multiply it by 255, round the result, then convert it to hexadecimal.
function RGBAToHexA(r,g,b,a) {
r = r.toString(16);
g = g.toString(16);
b = b.toString(16);
a = Math.round(a * 255).toString(16);
if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;
if (a.length == 1)
a = "0" + a;
return "#" + r + g + b + a;
}
To do this with one string (including with percentages), we can follow what we did earlier. Also note the extra step of splicing out a slash. Since CSS Colors Level 4 supports the syntax of rgba(r g b / a), this is where we allow it. Alpha values can now be percentages! This removes the 0-1-only shackles we used to have. Therefore, the for loop cycling through rgba shall include a part to wipe the % from the alpha without multiplying by 255 (when R is 3 for alpha). Soon we can use values like rgba(255 128 0 / 0.8) and rgba(100% 21% 100% / 30%)!
function RGBAToHexA(rgba) {
let sep = rgba.indexOf(",") > -1 ? "," : " ";
rgba = rgba.substr(5).split(")")[0].split(sep);
// Strip the slash if using space-separated syntax
if (rgba.indexOf("/") > -1)
rgba.splice(3,1);
for (let R in rgba) {
let r = rgba[R];
if (r.indexOf("%") > -1) {
let p = r.substr(0,r.length - 1) / 100;
if (R < 3) {
rgba[R] = Math.round(p * 255);
} else {
rgba[R] = p;
}
}
}
}
Then, where the channels are converted to hex, we adjust a to use an item of rgba[].
function RGBAToHexA(rgba) {
...
let r = (+rgba[0]).toString(16),
g = (+rgba[1]).toString(16),
b = (+rgba[2]).toString(16),
a = Math.round(+rgba[3] * 255).toString(16);
if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;
if (a.length == 1)
a = "0" + a;
return "#" + r + g + b + a;
}
Now the function supports the following:
rgba(255,25,2,0.5)
rgba(255 25 2 / 0.5)
rgba(50%,30%,10%,0.5)
rgba(50%,30%,10%,50%)
rgba(50% 30% 10% / 0.5)
rgba(50% 30% 10% / 50%)
Hex to RGB
We know that the length of hex values must either be 3 or 6 (plus #). In either case, we begin each red (r), green (g), and blue (b) value with "0x" to convert them to hex. If we provide a 3-digit value, we concatenate the same value twice for each channel. If it’s a 6-digit value, we concatenate the first two for red, next two for green, and last two for blue. To get the values for the final rgb() string, we prepend the variables with + to convert them from strings back to numbers, which will yield the decimals we need.
function hexToRGB(h) {
let r = 0, g = 0, b = 0;
// 3 digits
if (h.length == 4) {
r = "0x" + h[1] + h[1];
g = "0x" + h[2] + h[2];
b = "0x" + h[3] + h[3];
// 6 digits
} else if (h.length == 7) {
r = "0x" + h[1] + h[2];
g = "0x" + h[3] + h[4];
b = "0x" + h[5] + h[6];
}
return "rgb("+ +r + "," + +g + "," + +b + ")";
}
Output RGB with %s
If we want to return rgb() using percentages, then we can modify the function to utilize an optional isPct parameter like so:
function hexToRGB(h,isPct) {
let r = 0, g = 0, b = 0;
isPct = isPct === true;
if (h.length == 4) {
r = "0x" + h[1] + h[1];
g = "0x" + h[2] + h[2];
b = "0x" + h[3] + h[3];
} else if (h.length == 7) {
r = "0x" + h[1] + h[2];
g = "0x" + h[3] + h[4];
b = "0x" + h[5] + h[6];
}
if (isPct) {
r = +(r / 255 * 100).toFixed(1);
g = +(g / 255 * 100).toFixed(1);
b = +(b / 255 * 100).toFixed(1);
}
return "rgb(" + (isPct ? r + "%," + g + "%," + b + "%" : +r + "," + +g + "," + +b) + ")";
}
Under the last if statement, using +s will convert r, g, and b to numbers. Each toFixed(1) along with them will round the result to the nearest tenth. Additionally, we won’t have whole numbers with .0 or the decades old bug that produces numbers like 0.30000000000000004. Therefore, in the return, we omitted the +s right before the first r, g, and b to prevent NaNs caused by the %s. Now we can use hexToRGB("#ff0",true) to get rgb(100%,100%,0%)!
Hex (#rrggbbaa) to RGBA
The procedure for hex values with alpha should again be similar with the last. We simply detect a 4- or 8-digit value (plus #) then convert the alpha and divide it by 255. To get more precise output but not long decimal numbers for alpha, we can use toFixed(3).
function hexAToRGBA(h) {
let r = 0, g = 0, b = 0, a = 1;
if (h.length == 5) {
r = "0x" + h[1] + h[1];
g = "0x" + h[2] + h[2];
b = "0x" + h[3] + h[3];
a = "0x" + h[4] + h[4];
} else if (h.length == 9) {
r = "0x" + h[1] + h[2];
g = "0x" + h[3] + h[4];
b = "0x" + h[5] + h[6];
a = "0x" + h[7] + h[8];
}
a = +(a / 255).toFixed(3);
return "rgba(" + +r + "," + +g + "," + +b + "," + a + ")";
}
Output RGBA with %s
For a version that outputs percentages, we can do what we did in hexToRGB()—switch r, g, and b to 0–100% when isPct is true.
function hexAToRGBA(h,isPct) {
let r = 0, g = 0, b = 0, a = 1;
isPct = isPct === true;
// Handling of digits
...
if (isPct) {
r = +(r / 255 * 100).toFixed(1);
g = +(g / 255 * 100).toFixed(1);
b = +(b / 255 * 100).toFixed(1);
}
a = +(a / 255).toFixed(3);
return "rgba(" + (isPct ? r + "%," + g + "%," + b + "%," + a : +r + "," + +g + "," + +b + "," + a) + ")";
}
Here’s a quick fix if the alpha ought to be a percentage, too: move the statement where a is redefined above the last if statement. Then in that statement, modify a to be like r, g, and b. When isPct is true, a must also gain the %.
function hexAToRGBA(h,isPct) {
...
a = +(a / 255).toFixed(3);
if (isPct) {
r = +(r / 255 * 100).toFixed(1);
g = +(g / 255 * 100).toFixed(1);
b = +(b / 255 * 100).toFixed(1);
a = +(a * 100).toFixed(1);
}
return "rgba(" + (isPct ? r + "%," + g + "%," + b + "%," + a + "%" : +r + "," + +g + "," + +b + "," + a) + ")";
}
When we enter #7f7fff80 now, we should get rgba(127,127,255,0.502) or rgba(49.8%,49.8%,100%,50.2%).
RGB to HSL
Obtaining HSL values from RGB or hex is a bit more challenging because there’s a larger formula involved. First, we must divide the red, green, and blue by 255 to use values between 0 and 1. Then we find the minimum and maximum of those values (cmin and cmax) as well as the difference between them (delta). We need that result as part of calculating the hue and saturation. Right after the delta, let’s initialize the hue (h), saturation (s), and lightness (l).
function RGBToHSL(r,g,b) {
// Make r, g, and b fractions of 1
r /= 255;
g /= 255;
b /= 255;
// Find greatest and smallest channel values
let cmin = Math.min(r,g,b),
cmax = Math.max(r,g,b),
delta = cmax - cmin,
h = 0,
s = 0,
l = 0;
}
Next, we need to calculate the hue, which is to be determined by the greatest channel value in cmax (or if all channels are the same). If there is no difference between the channels, the hue will be 0. If cmax is the red, then the formula will be ((g - b) / delta) % 6. If green, then (b - r) / delta + 2. Then, if blue, (r - g) / delta + 4. Finally, multiply the result by 60 (to get the degree value) and round it. Since hues shouldn’t be negative, we add 360 to it, if needed.
function RGBToHSL(r,g,b) {
...
// Calculate hue
// No difference
if (delta == 0)
h = 0;
// Red is max
else if (cmax == r)
h = ((g - b) / delta) % 6;
// Green is max
else if (cmax == g)
h = (b - r) / delta + 2;
// Blue is max
else
h = (r - g) / delta + 4;
h = Math.round(h * 60);
// Make negative hues positive behind 360°
if (h < 0)
h += 360;
}
All that’s left is the saturation and lightness. Let’s calculate the lightness before we do the saturation, as the saturation will depend on it. It’s the sum of the maximum and minimum channel values cut in half ((cmax + cmin) / 2). Then delta will determine what the saturation will be. If it’s 0 (no difference between cmax and cmin), then the saturation is automatically 0. Otherwise, it’ll be 1 minus the absolute value of twice the lightness minus 1 (1 - Math.abs(2 * l - 1)). Once we have these values, we must convert them to values out of 100%, so we multiply them by 100 and round to the nearest tenth. Now we can string together our hsl().
function RGBToHSL(r,g,b) {
...
// Calculate lightness
l = (cmax + cmin) / 2;
// Calculate saturation
s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
// Multiply l and s by 100
s = +(s * 100).toFixed(1);
l = +(l * 100).toFixed(1);
return "hsl(" + h + "," + s + "%," + l + "%)";
}
RGB in String
For one string, split the argument by comma or space, strip the %s, and localize r, g, and b like we did before.
function RGBToHSL(rgb) {
let sep = rgb.indexOf(",") > -1 ? "," : " ";
rgb = rgb.substr(4).split(")")[0].split(sep);
for (let R in rgb) {
let r = rgb[R];
if (r.indexOf("%") > -1)
rgb[R] = Math.round(r.substr(0,r.length - 1) / 100 * 255);
}
// Make r, g, and b fractions of 1
let r = rgb[0] / 255,
g = rgb[1] / 255,
b = rgb[2] / 255;
...
}
RGBA to HSLA
Compared to what we just did to convert RGB to HSL, the alpha counterpart will be basically nothing! We just reuse the code for RGB to HSL (the multi-argument version), leave a alone, and pass a to the returned HSLA. Keep in mind it should be between 0 and 1.
function RGBAToHSLA(r,g,b,a) {
// Code for RGBToHSL(r,g,b) before return
...
return "hsla(" + h + "," + s + "%," +l + "%," + a + ")";
}
RGBA in String
For string values, we apply the splitting and stripping logic again but use the fourth item in rgba for a. Remember the new rgba(r g b / a) syntax? We’re employing the acceptance of it as we did for RGBAToHexA(). Then the rest of the code is the normal RGB-to-HSL conversion.
function RGBAToHSLA(rgba) {
let sep = rgba.indexOf(",") > -1 ? "," : " ";
rgba = rgba.substr(5).split(")")[0].split(sep);
// Strip the slash if using space-separated syntax
if (rgba.indexOf("/") > -1)
rgba.splice(3,1);
for (let R in rgba) {
let r = rgba[R];
if (r.indexOf("%") > -1) {
let p = r.substr(0,r.length - 1) / 100;
if (R < 3) {
rgba[R] = Math.round(p * 255);
} else {
rgba[R] = p;
}
}
}
// Make r, g, and b fractions of 1
let r = rgba[0] / 255,
g = rgba[1] / 255,
b = rgba[2] / 255,
a = rgba[3];
// Rest of RGB-to-HSL logic
...
}
Wish to leave the alpha as is? Remove the else statement from the for loop.
for (let R in rgba) {
let r = rgba[R];
if (r.indexOf("%") > -1) {
let p = r.substr(0,r.length - 1) / 100;
if (R < 3) {
rgba[R] = Math.round(p * 255);
}
}
}
HSL to RGB
It takes slightly less logic to convert HSL back to RGB than the opposite way. Since we’ll use a range of 0–100 for the saturation and lightness, the first step is to divide them by 100 to values between 0 and 1. Next, we find chroma (c), which is color intensity, so that’s (1 - Math.abs(2 * l - 1)) * s. Then we use x for the second largest component (first being chroma), the amount to add to each channel to match the lightness (m), and initialize r, g, b.
function HSLToRGB(h,s,l) {
// Must be fractions of 1
s /= 100;
l /= 100;
let c = (1 - Math.abs(2 * l - 1)) * s,
x = c * (1 - Math.abs((h / 60) % 2 - 1)),
m = l - c/2,
r = 0,
g = 0,
b = 0;
}
The hue will determine what the red, green, and blue should be depending on which 60° sector of the color wheel it lies.
The color wheel divided into 60° segments
Then c and x shall be assigned as shown below, leaving one channel at 0. To get the final RGB value, we add m to each channel, multiply it by 255, and round it.
function HSLToRGB(h,s,l) {
...
if (0 <= h && h < 60) {
r = c; g = x; b = 0;
} else if (60 <= h && h < 120) {
r = x; g = c; b = 0;
} else if (120 <= h && h < 180) {
r = 0; g = c; b = x;
} else if (180 <= h && h < 240) {
r = 0; g = x; b = c;
} else if (240 <= h && h < 300) {
r = x; g = 0; b = c;
} else if (300 <= h && h < 360) {
r = c; g = 0; b = x;
}
r = Math.round((r + m) * 255);
g = Math.round((g + m) * 255);
b = Math.round((b + m) * 255);
return "rgb(" + r + "," + g + "," + b + ")";
}
HSL in String
For the single string version, we modify the first few statements basically the same way we did for RGBToHSL(r,g,b). Remove s /= 100; and l /= 100; and we’ll use the new statements to wipe the first 4 characters and the ) for our array of HSL values, then the %s from s and l before dividing them by 100.
function HSLToRGB(hsl) {
let sep = hsl.indexOf(",") > -1 ? "," : " ";
hsl = hsl.substr(4).split(")")[0].split(sep);
let h = hsl[0],
s = hsl[1].substr(0,hsl[1].length - 1) / 100,
l = hsl[2].substr(0,hsl[2].length - 1) / 100;
...
}
The next handful of statements shall handle hues provided with a unit—degrees, radians, or turns. We multiply radians by 180/? and turns by 360. If the result ends up over 360, we compound modulus divide to keep it within the scope. All of this will happen before we deal with c, x, and m.
function HSLToRGB(hsl) {
...
// Strip label and convert to degrees (if necessary)
if (h.indexOf("deg") > -1)
h = h.substr(0,h.length - 3);
else if (h.indexOf("rad") > -1)
h = Math.round(h.substr(0,h.length - 3) * (180 / Math.PI));
else if (h.indexOf("turn") > -1)
h = Math.round(h.substr(0,h.length - 4) * 360);
// Keep hue fraction of 360 if ending up over
if (h >= 360)
h %= 360;
// Conversion to RGB begins
...
}
After implementing the steps above, now the following can be safely used:
hsl(180 100% 50%)
hsl(180deg,100%,50%)
hsl(180deg 100% 50%)
hsl(3.14rad,100%,50%)
hsl(3.14rad 100% 50%)
hsl(0.5turn,100%,50%)
hsl(0.5turn 100% 50%)
Whew, that’s quite the flexibility!
Output RGB with %s
Similarly, we can modify this function to return percent values just like we did in hexToRGB().
function HSLToRGB(hsl,isPct) {
let sep = hsl.indexOf(",") > -1 ? "," : " ";
hsl = hsl.substr(4).split(")")[0].split(sep);
isPct = isPct === true;
...
if (isPct) {
r = +(r / 255 * 100).toFixed(1);
g = +(g / 255 * 100).toFixed(1);
b = +(b / 255 * 100).toFixed(1);
}
return "rgb("+ (isPct ? r + "%," + g + "%," + b + "%" : +r + "," + +g + "," + +b) + ")";
}
HSLA to RGBA
Once again, handling alphas will be a no-brainer. We can reapply the code for the original HSLToRGB(h,s,l) and add a to the return.
function HSLAToRGBA(h,s,l,a) {
// Code for HSLToRGB(h,s,l) before return
...
return "rgba(" + r + "," + g + "," + b + "," + a + ")";
}
HSLA in String
Changing it to one argument, the way we’ll handle strings here will be not too much different than what we did earlier. A new HSLA syntax from Colors Level 4 uses (value value value / value) just like RGBA, so having the code to handle it, we’ll be able to plug in something like hsla(210 100% 50% / 0.5) here.
function HSLAToRGBA(hsla) {
let sep = hsla.indexOf(",") > -1 ? "," : " ";
hsla = hsla.substr(5).split(")")[0].split(sep);
if (hsla.indexOf("/") > -1)
hsla.splice(3,1);
let h = hsla[0],
s = hsla[1].substr(0,hsla[1].length - 1) / 100,
l = hsla[2].substr(0,hsla[2].length - 1) / 100,
a = hsla[3];
if (h.indexOf("deg") > -1)
h = h.substr(0,h.length - 3);
else if (h.indexOf("rad") > -1)
h = Math.round(h.substr(0,h.length - 3) * (180 / Math.PI));
else if (h.indexOf("turn") > -1)
h = Math.round(h.substr(0,h.length - 4) * 360);
if (h >= 360)
h %= 360;
...
}
Furthermore, these other combinations have become possible:
hsla(180,100%,50%,50%)
hsla(180 100% 50% / 50%)
hsla(180deg,100%,50%,0.5)
hsla(3.14rad,100%,50%,0.5)
hsla(0.5turn 100% 50% / 50%)
RGBA with %s
Then we can replicate the same logic for outputting percentages, including alpha. If the alpha should be a percentage (searched in pctFound), here’s how we can handle it:
If r, g, and b are to be converted to percentages, then a should be multiplied by 100, if not already a percentage. Otherwise, drop the %, and it’ll be added back in the return.
If r, g, and b should be left alone, then remove the % from a and divide a by 100.
function HSLAToRGBA(hsla,isPct) {
// Code up to slash stripping
...
isPct = isPct === true;
// h, s, l, a defined to rounding of r, g, b
...
let pctFound = a.indexOf("%") > -1;
if (isPct) {
r = +(r / 255 * 100).toFixed(1);
g = +(g / 255 * 100).toFixed(1);
b = +(b / 255 * 100).toFixed(1);
if (!pctFound) {
a *= 100;
} else {
a = a.substr(0,a.length - 1);
}
} else if (pctFound) {
a = a.substr(0,a.length - 1) / 100;
}
return "rgba("+ (isPct ? r + "%," + g + "%," + b + "%," + a + "%" : +r + ","+ +g + "," + +b + "," + +a) + ")";
}
Hex to HSL
You might think this one and the next are crazier processes than the others, but they merely come in two parts with recycled logic. First, we convert the hex to RGB. That gives us the base 10s we need to convert to HSL.
function hexToHSL(H) {
// Convert hex to RGB first
let r = 0, g = 0, b = 0;
if (H.length == 4) {
r = "0x" + H[1] + H[1];
g = "0x" + H[2] + H[2];
b = "0x" + H[3] + H[3];
} else if (H.length == 7) {
r = "0x" + H[1] + H[2];
g = "0x" + H[3] + H[4];
b = "0x" + H[5] + H[6];
}
// Then to HSL
r /= 255;
g /= 255;
b /= 255;
let cmin = Math.min(r,g,b),
cmax = Math.max(r,g,b),
delta = cmax - cmin,
h = 0,
s = 0,
l = 0;
if (delta == 0)
h = 0;
else if (cmax == r)
h = ((g - b) / delta) % 6;
else if (cmax == g)
h = (b - r) / delta + 2;
else
h = (r - g) / delta + 4;
h = Math.round(h * 60);
if (h < 0)
h += 360;
l = (cmax + cmin) / 2;
s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
s = +(s * 100).toFixed(1);
l = +(l * 100).toFixed(1);
return "hsl(" + h + "," + s + "%," + l + "%)";
}
Hex (#rrggbbaa) to HSLA
There aren’t too many lines that change in this one. We’ll repeat what we recently did to get the alpha by converting the hex, but won’t divide it by 255 right away. First, we must get the hue, saturation, and lightness as we did in the other to-HSL functions. Then, before the ending return, we divide the alpha and set the decimal places.
function hexAToHSLA(H) {
let r = 0, g = 0, b = 0, a = 1;
if (H.length == 5) {
r = "0x" + H[1] + H[1];
g = "0x" + H[2] + H[2];
b = "0x" + H[3] + H[3];
a = "0x" + H[4] + H[4];
} else if (H.length == 9) {
r = "0x" + H[1] + H[2];
g = "0x" + H[3] + H[4];
b = "0x" + H[5] + H[6];
a = "0x" + H[7] + H[8];
}
// Normal conversion to HSL
...
a = (a / 255).toFixed(3);
return "hsla("+ h + "," + s + "%," + l + "%," + a + ")";
}
HSL to Hex
This one starts as a conversion to RGB, but there’s an extra step to the Math.round()s of converting the RGB results to hex.
function HSLToHex(h,s,l) {
s /= 100;
l /= 100;
let c = (1 - Math.abs(2 * l - 1)) * s,
x = c * (1 - Math.abs((h / 60) % 2 - 1)),
m = l - c/2,
r = 0,
g = 0,
b = 0;
if (0 <= h && h < 60) {
r = c; g = x; b = 0;
} else if (60 <= h && h < 120) {
r = x; g = c; b = 0;
} else if (120 <= h && h < 180) {
r = 0; g = c; b = x;
} else if (180 <= h && h < 240) {
r = 0; g = x; b = c;
} else if (240 <= h && h < 300) {
r = x; g = 0; b = c;
} else if (300 <= h && h < 360) {
r = c; g = 0; b = x;
}
// Having obtained RGB, convert channels to hex
r = Math.round((r + m) * 255).toString(16);
g = Math.round((g + m) * 255).toString(16);
b = Math.round((b + m) * 255).toString(16);
// Prepend 0s, if necessary
if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;
return "#" + r + g + b;
}
HSL in String
Even the first few lines of this function will be like those in HSLToRGB() if we changed it to accept a single string. This is how we’ve been obtaining the hue, saturation, and lightness separately in the first place. Let’s not forget the step to remove the hue label and convert to degrees, too. All of this will be in place of s /= 100; and l /= 100;.
function HSLToHex(hsl) {
let sep = hsl.indexOf(",") > -1 ? "," : " ";
hsl = hsl.substr(4).split(")")[0].split(sep);
let h = hsl[0],
s = hsl[1].substr(0,hsl[1].length - 1) / 100,
l = hsl[2].substr(0,hsl[2].length - 1) / 100;
// Strip label and convert to degrees (if necessary)
if (h.indexOf("deg") > -1)
h = h.substr(0,h.length - 3);
else if (h.indexOf("rad") > -1)
h = Math.round(h.substr(0,h.length - 3) * (180 / Math.PI));
else if (h.indexOf("turn") > -1)
h = Math.round(h.substr(0,h.length - 4) * 360);
if (h >= 360)
h %= 360;
...
}
HSLA to Hex (#rrggbbaa)
Adding alpha to the mix, we convert a to hex and add a fourth if to prepend a 0, if necessary. You probably already familiar with this logic because we last used it in RGBAToHexA().
function HSLAToHexA(h,s,l,a) {
// Repeat code from HSLToHex(h,s,l) until 3 `toString(16)`s
...
a = Math.round(a * 255).toString(16);
if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;
if (a.length == 1)
a = "0" + a;
return "#" + r + g + b + a;
}
HSLA in String
Finally, the lines of the single argument version up to a = hsla[3] are no different than those of HSLAToRGBA().
function HSLAToHexA(hsla) {
let sep = hsla.indexOf(",") > -1 ? "," : " ";
hsla = hsla.substr(5).split(")")[0].split(sep);
// Strip the slash
if (hsla.indexOf("/") > -1)
hsla.splice(3,1);
let h = hsla[0],
s = hsla[1].substr(0,hsla[1].length - 1) / 100,
l = hsla[2].substr(0,hsla[2].length - 1) / 100,
a = hsla[3];
...
}
Built-in Names
To convert a named color to RGB, hex, or HSL, you might consider turning this table of 140+ names and hex values into a massive object at the start. The truth is that we really don’t need one because here’s what we can do:
Create an element
Give it a text color
Obtain the value of that property
Remove the element
Return the stored color value, which will be in RGB by default
So, our function to get RGB will only be seven statements!
function nameToRGB(name) {
// Create fake div
let fakeDiv = document.createElement("div");
fakeDiv.style.color = name;
document.body.appendChild(fakeDiv);
// Get color of div
let cs = window.getComputedStyle(fakeDiv),
pv = cs.getPropertyValue("color");
// Remove div after obtaining desired color value
document.body.removeChild(fakeDiv);
return pv;
}
Let’s go even further. How about we change the output to hex instead?
function nameToHex(name) {
// Get RGB from named color in temporary div
let fakeDiv = document.createElement("div");
fakeDiv.style.color = name;
document.body.appendChild(fakeDiv);
let cs = window.getComputedStyle(fakeDiv),
pv = cs.getPropertyValue("color");
document.body.removeChild(fakeDiv);
// Code ripped from RGBToHex() (except pv is substringed)
let rgb = pv.substr(4).split(")")[0].split(","),
r = (+rgb[0]).toString(16),
g = (+rgb[1]).toString(16),
b = (+rgb[2]).toString(16);
if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;
return "#" + r + g + b;
}
Or, why not HSL? ?
function nameToHSL(name) {
let fakeDiv = document.createElement("div");
fakeDiv.style.color = name;
document.body.appendChild(fakeDiv);
let cs = window.getComputedStyle(fakeDiv),
pv = cs.getPropertyValue("color");
document.body.removeChild(fakeDiv);
// Code ripped from RGBToHSL() (except pv is substringed)
let rgb = pv.substr(4).split(")")[0].split(","),
r = rgb[0] / 255,
g = rgb[1] / 255,
b = rgb[2] / 255,
cmin = Math.min(r,g,b),
cmax = Math.max(r,g,b),
delta = cmax - cmin,
h = 0,
s = 0,
l = 0;
if (delta == 0)
h = 0;
else if (cmax == r)
h = ((g - b) / delta) % 6;
else if (cmax == g)
h = (b - r) / delta + 2;
else
h = (r - g) / delta + 4;
h = Math.round(h * 60);
if (h < 0)
h += 360;
l = (cmax + cmin) / 2;
s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
s = +(s * 100).toFixed(1);
l = +(l * 100).toFixed(1);
return "hsl(" + h + "," + s + "%," + l + "%)";
}
In the long run, every conversion from a name becomes a conversion from RGB after cracking the name.
Validating Colors
In all these functions, there haven’t been any measures to prevent or correct ludicrous input (say hues over 360 or percentages over 100). If we’re only manipulating pixels on a fetched using getImageData(), validation of color values isn’t necessary before converting because they’ll be correct no matter what. If we’re creating a color conversion tool where users supply the color, then validation would be much needed.
It’s easy to handle improper input for channels as separate arguments, like this for RGB:
// Correct red
if (r > 255)
r = 255;
else if (r < 0)
r = 0;
If validating a whole string, then a regular expression is needed. For instance, this is the RGBToHex() function given a validation step with an expression:
function RGBToHex(rgb) {
// Expression for rgb() syntaxes
let ex = /^rgb((((((((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5]),s?)){2}|((((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5])s)){2})((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5]))|((((([1-9]?d(.d+)?)|100|(.d+))%,s?){2}|((([1-9]?d(.d+)?)|100|(.d+))%s){2})(([1-9]?d(.d+)?)|100|(.d+))%)))$/i;
if (ex.test(rgb)) {
// Logic to convert RGB to hex
...
} else {
// Something to do if color is invalid
}
}
To test other types of values, below is a table of expressions to cover both opaque and alpha-enabled:
Looking at the expressions for RGB(A) and HSL(A), you probably have big eyes right now; these were made comprehensive enough to include most of the new syntaxes from CSS Colors Level 4. Hex, on the other hand, doesn’t need expressions as long as the others because of only digit counts. In a moment, we’ll dissect these and decipher the parts. Note that case-insensitive values (/i) pass all these.
Because rgb() accepts either all integers or all percentages, both cases are covered. In the outmost group, between the ^rgb( and )$, there are inner groups for both integers and percentages, all comma-spaces or spaces only as separators:
In the first half, we accept two instances of integers for red and green from 0–99 or 111-199 ((1?[1-9]?d)), 100–109 (10d), 200-249 ((2[0-4]d)), or 250–255 (25[0-5]). We couldn’t simply do d{1,3} because values like 03 or 017 and those greater than 255 shouldn’t be allowed. After that goes the comma and optional space (,s?). On the other side of the |, after the first {2} (which indicates two instances of integers), we check for the same thing with space separators if the left side is false. Then for blue, the same should be accepted, but without a separator.
In the other half, acceptable values for percentages, including floats, should either be 0–99, explicitly 100 and not a float, or floats under 1 with the 0 dropped. Therefore, the segment here is (([1-9]?d(.d+)?)|100|(.d+)), and it appears three times; twice with separator (,s?){2}, %s){2}), once without.
It is legal to use percentages without space separators (rgb(100%50%10%) for instance) in CSS, but the functions we wrote don’t support that. The same goes for rgba(100%50%10%/50%), hsl(40 100%50%), and hsla(40 100%50%/0.5). This could very well be a plus for code golfing and minification!
The next expression is very similar to the pervious, but three instances of integers (((((1?[1-9]?d)|10d|(2[0-4]d)|25[0-5]),s?){3})) or percentages ((((([1-9]?d(.d+)?)|100|(.d+))%,s?){3})), plus comma optional space are checked. Otherwise, it looks for the same thing but with space separators, plus a slash and space (/s) after the blue. Next to that is ((0?.d+)|[01]|(([1-9]?d(.d+)?)|100|(.d+))%) where we accept floats with or without the first 0 ((0?.d+)), 0 or 1 ([01]) on the dot, or 0–100% ((([1-9]?d(.d+)?)|100|(.d+))%).
For both hex—with and without alpha—instances of numbers or letters a–f ([da-f]) are accepted. Then one or two instances of this are counted for either short or longhand values supplied (#rgb or #rrggbb). As an illustration, we have this same short pattern: /^#([da-f]{n}){1,2}$/i. Simply change n to 3 or 4.
([12]?[1-9]?d) covers 0–99, 110–199, and 210–299. [12]0d covers 110–109 and 200–209. Then (3[0-5]d) takes care of 300–359. The reason for this division of ranges is similar to that of integers in the rgb() syntax: ruling out zeros coming first and values greater than the maximum. Since hues can be floating point numbers, the first (.d+)? is for that.
Next to the | after the aforementioned segment of code, the second (.d+) is for floats without a leading zero.
Now let’s move up a level and decipher the next small chunk:
(deg)?|(0|0?.d+)turn|(([0-6.d+)?)|(.d+))rad
This contains the labels we can use for the hue—degrees, turns, or radians. We can include all or none of deg. Values in turn must be under 1. For radians, we can accept any float between 0–7. We do know, however, that one 360° turn is 2?, and it stops approximately at 6.28. You may think 6.3 and over shouldn’t be accepted. Because 2? is an irrational number, it would be too messy for this example to try to satisfy every decimal place provided by the JavaScript console. Besides, we have this snippet in our HSLTo_() functions as a second layer of security if hues 360° or over were to happen:
// Keep hue fraction of 360 if ending up over
if (h >= 360)
h %= 360;
Now let’s move up a level and decipher the second chunk:
(,s?(([1-9]?d(.d+)?)|100|(.d+))%){2}
We’re counting two instances of comma-space-percentages for the saturation and lightness (space optional). In the group after the ,s?, we test for values 0–99 with or without decimal points (([1-9]?d(.d+)?)), exactly 100, or floats under 1 without the leading 0 ((.d+)).
The last part the HSL expression, before the ending ()$/i), is a similar expression if spaces are the only separator:
(s(([1-9]?d(.d+)?)|100|(.d+))%){2}
s is in the beginning instead of ,s?. Then in the HSLA expression, this same chunk is inside another group with ,s? after its {2}.
((,s?(([1-9]?d(.d+)?)|100|(.d+))%){2},s?)
That counts the comma-space between the lightness and alpha. Then if we have spaces as separators, we need to check for a space-slash-space (s/s) after counting two instances of space and a percentage.
((s(([1-9]?d(.d+)?)|100|(.d+))%){2}s/s))
After that, we have this left to check the alpha value:
(((0?.d+)|[01])|(([1-9]?d(.d+)?)|100|(.d+))%)
Matches for (0?.d+) include floats under 1 with or without the leading 0, 0 or 1 for [01], and 0–100%.
Conclusion
If your current challenge is to convert one color space to another, you now have some ideas on how to approach it. Because it would be tiresome to walk through converting every color space ever invented in one post, we discussed the most practical and browser-supported ones. If you’d like to go beyond supported color spaces (say CMYK, XYZ, or CIE L*a*b*), EasyRGB) provides an amazing set of code-ready formulas.
To see all the conversions demonstrated here, I’ve set up a CodePen demo that shows inputs and outputs in a table. You can try different colors in lines 2–10 and see the complete functions in the JavaScript panel.
Don’t miss this video by Heydon that digs into CSS layouts. It’s great how he combines fundamental knowledge, like the way elements flow, wrap, and can have margin with new layout methods like flexbox and grid (with specific examples). Of particular note is the clear demonstration of how flexbox and grid help avoid the need to constantly intervene with media queries in order to affect responsive layouts.
Within the arsenal of every WordPress developer exists a toolbox of plugins used to implement key features on a website. Forms, up until now, have been a point of contention for most developers, given that no form plugins have offered seamless integration with existing website code. Therefore, forms often become an alien chunk of code requiring custom and time-consuming stylization.
WS Form is a developer-focused WordPress form plugin, which outputs framework-ready, responsive HTML5 code. It allows you to rapidly create forms using an innovative layout editor and a plethora of development features.
Front-End, Framework-Compatible HTML from a Layout Editor
If you’re developing or implementing a theme using Bootstrap (versions 3 & 4) or Foundation (versions 5, 6 & 6.4+), WS Form will output code that is native to those frameworks. For themes that do not use those frameworks, a fallback framework is included that is fully responsive and easy for developers to style.
The WS Form layout editor allows you to edit your form at any breakpoint. Form elements are dragged and dropped into the form, and all responsive CSS classes are handled for you. For developers wanting additional control, each field type comes with a vast array of settings, including the ability to add your own wrapper and field-level classes.
And within WS Form, time travel is real. The undo history feature allows you to step back to any point in your form development and continue from that point forward.
Introducing the First Form Debug Console
WS Form is the first WordPress form plugin to offer a dedicated debug console for developers.
A time-consuming task, when developing any form, is having to repeatedly populate a form to test it. WS Form is the first WordPress form plugin to offer the ability to automatically populate a form. Simply click “Populate” in the debug console, and the form will be pre-populated with different sample data each time. This dramatically speeds up development time, particularly with larger, multi-tab forms.
The console provides per form instance activity and error logging, as well as the ability to reload a form while still on the same web page.
Extensive HTML5 Input Type Support
WS Form includes settings for all form input types. Settings include everything from default values and placeholder text to custom validation messages and datalists. In addition to elementary HTML5 input types, WS Form offers additional fields, such as reCAPTCHA, signatures, and even e-commerce and payment buttons.
Some HTML5 input types, such as date and color selectors, are still not supported in all web browsers. WS Form overcomes this obstacle by checking for native support, and if unavailable, a suitable alternative component is loaded. You have the option of loading that component from your web server or from a CDN.
Conditional logic allows you to make a form interactive and improve usability. For example, you could opt to only show shipping address fields if a checkbox is checked, or you could show an error message if a password confirmation does not match.
WS Form comes with an extensive array of options when creating if, then, and else conditions at form, tab, section, and field levels. Furthermore, conditional options are context sensitive, so, for example, color fields allow you to fire behavior if the hue or lightness of that field matches specified conditions. WS Form even allows you to fire actions, such as sending an email or showing a message, if any condition is met. This could be useful for automatically saving a form as a user steps through tabs on a form.
An Ever-Expanding Library of Form Actions
WS Form actions are fired whenever a form is saved or submitted by a user. Actions can also be fired using conditional logic.
The actions include:
Sending emails
Showing messages (e.g., a thank you message)
Running JavaScript
Firing a WordPress hook (actions or filters)
Initiating WordPress GDPR functionality, such as a data export or erasure request
Building a WordPress form in WS Form means you can rapidly prototype and implement forms. With responsive HTML5 code, automatic framework compatibility, and advanced conditional logic, just to name a few of the features, WS Form is changing the way WordPress forms can enhance and empower a website.
Use coupon code CSST20 to receive 20% off any WS Form PRO product!
Within the arsenal of every WordPress developer exists a toolbox of plugins used to implement key features on a website. Forms, up until now, have been a point of contention for most developers, given that no form plugins have offered seamless integration with existing website code. Therefore, forms often become an alien chunk of code requiring custom and time-consuming stylization.
WS Form is a developer-focused WordPress form plugin, which outputs framework-ready, responsive HTML5 code. It allows you to rapidly create forms using an innovative layout editor and a plethora of development features.
Front-End, Framework-Compatible HTML from a Layout Editor
If you’re developing or implementing a theme using Bootstrap (versions 3 & 4) or Foundation (versions 5, 6 & 6.4+), WS Form will output code that is native to those frameworks. For themes that do not use those frameworks, a fallback framework is included that is fully responsive and easy for developers to style.
The WS Form layout editor allows you to edit your form at any breakpoint. Form elements are dragged and dropped into the form, and all responsive CSS classes are handled for you. For developers wanting additional control, each field type comes with a vast array of settings, including the ability to add your own wrapper and field-level classes.
And within WS Form, time travel is real. The undo history feature allows you to step back to any point in your form development and continue from that point forward.
Introducing the First Form Debug Console
WS Form is the first WordPress form plugin to offer a dedicated debug console for developers.
A time-consuming task, when developing any form, is having to repeatedly populate a form to test it. WS Form is the first WordPress form plugin to offer the ability to automatically populate a form. Simply click “Populate” in the debug console, and the form will be pre-populated with different sample data each time. This dramatically speeds up development time, particularly with larger, multi-tab forms.
The console provides per form instance activity and error logging, as well as the ability to reload a form while still on the same web page.
Extensive HTML5 Input Type Support
WS Form includes settings for all form input types. Settings include everything from default values and placeholder text to custom validation messages and datalists. In addition to elementary HTML5 input types, WS Form offers additional fields, such as reCAPTCHA, signatures, and even e-commerce and payment buttons.
Some HTML5 input types, such as date and color selectors, are still not supported in all web browsers. WS Form overcomes this obstacle by checking for native support, and if unavailable, a suitable alternative component is loaded. You have the option of loading that component from your web server or from a CDN.
Conditional logic allows you to make a form interactive and improve usability. For example, you could opt to only show shipping address fields if a checkbox is checked, or you could show an error message if a password confirmation does not match.
WS Form comes with an extensive array of options when creating if, then, and else conditions at form, tab, section, and field levels. Furthermore, conditional options are context sensitive, so, for example, color fields allow you to fire behavior if the hue or lightness of that field matches specified conditions. WS Form even allows you to fire actions, such as sending an email or showing a message, if any condition is met. This could be useful for automatically saving a form as a user steps through tabs on a form.
An Ever-Expanding Library of Form Actions
WS Form actions are fired whenever a form is saved or submitted by a user. Actions can also be fired using conditional logic.
The actions include:
Sending emails
Showing messages (e.g., a thank you message)
Running JavaScript
Firing a WordPress hook (actions or filters)
Initiating WordPress GDPR functionality, such as a data export or erasure request
Building a WordPress form in WS Form means you can rapidly prototype and implement forms. With responsive HTML5 code, automatic framework compatibility, and advanced conditional logic, just to name a few of the features, WS Form is changing the way WordPress forms can enhance and empower a website.
Use coupon code CSST20 to receive 20% off any WS Form PRO product!
Designers create handwriting-based connected cursive fonts for a variety of reasons: to immortalize the loops and swirls of a loved one’s handwriting, to digitize the penmanship of a person or document of historic significance, or to transform charming handwriting into a creative asset that can be licensed.
Let’s say you found a beautiful old handwriting specimen you want to digitize. You might presume you can trace individual letters, then seamlessly convert those tracings into a font. I will confess that was my assumption before I began to work on my first font. I had not taken into account the myriad of thoughtful and intentional decisions required to transform the specimen into an artful and functional font.
Before you begin the process of digitizing your specimen, it would be worthwhile to ask yourself a few questions about your goals and intent. Think of it as writing a creative brief for your project. Begin by assessing the importance of historical accuracy. Then conduct a close examination of the specimen: look at the idiosyncrasies in the handwriting, the variation in shape and position of individual letters, the method for connecting letters, and the texture. Possessing a keen familiarity of your specimen will allow you to make informed decisions about aesthetics as you design your font.
One of the biggest decisions you will need to make is whether you want to capture every nuance of your handwriting specimen, or if you want to design something inspired by that handwriting. It is like watching a movie “based on a true story” versus one “inspired by real events.” In the first scenario, you can expect the movie (or font) maintains a higher degree of factual integrity than the second option, where the director (or designer) may take wide-ranging creative liberties.
If you choose to replicate your specimen with utmost precision, be aware that rigorously honoring accuracy may mean compromising legibility. “Old scripts, in particular, include letterforms that are less legible — even virtually illegible, like the old-style long s — than in modern handwriting,” notes Brian Willson, who has designed more than two dozen fonts based on the handwriting of notable figures such as Abigail Adams, Frederick Douglass, and Sam Houston.
Compare the similarities in these three fonts based on (or inspired by) the handwriting of Abraham Lincoln. Although 1863 Gettysburg (the font shown on the far right) was inspired by documents written by President Lincoln, the designer’s stated goal was not to replicate Lincoln’s exact writing, but to create a font that represented the era. (From left to right: LD Abe Lincoln by Lettering Delights/Illustration Ink, a Lincoln by Steve Woolf, and 1863 Gettysburg designed by GLC.) (Large preview)
You may find you want to make thoughtful revisions to strike a balance between historical accuracy and optimized legibility. As I designed the P22 Marcel Script, which is a connected cursive font based on handwritten WWII love letters, I chose to make small revisions to improve legibility.
The font retains the essential character of the original writing, but it is not a precise replica. Many of the original j‘s, for example, did not have a tittle (a dot), and the original lowercase p did not have a closed curve on the bottom of the bowl. It looked like a hybrid between a p and an n. Knowing some people struggle to read cursive writing, I chose to dot the j and revise the shape of the p.
Note the small revisions to improve legibility. (From left to right: The original handwriting specimen, a close-up of the greeting “Mes chères petites”, and a sample of the font P22 Marcel Script.) (Large preview)
Does Your Source Document Include Enough Material To Work With?
John Hancock had a fantastic signature, but designing an entire font based on the eight letters in his name — J, o, h, n, H, a, c, and k — would be a challenge. Assess whether your specimen is complete enough to support an entire font. Does it include both upper and lowercase letters? How about numbers?
When designing the font based on the handwriting of Jane Austen, designer Pia Frauss discovered she did not have a handwritten letter X to reference. If, like Frauss, you have a specimen that is mostly complete, you should be able to extrapolate what a specific letter might have looked like. That skill will also be necessary when it comes time to design new glyphs — the term for a specific character in a font file — like the Euro, which has only existed since 1999.
If your specimen only has a limited set of characters, gauge whether you feel comfortable designing the missing letters. If not, consider finding a handwritten specimen that is similar in style, then pulling the missing letters from the supplementary specimen.
What Idiosyncrasies Make The Handwriting Special?
Are crossbars unusually high, low, or angled? Are ascenders or descenders abnormally long or short? Are letters strikingly narrow or wide? Do specific letters extend above or below the baseline? Do letters loop in unusual ways?
If your goal is to create a historically accurate font, you will want to take care to ensure those idiosyncrasies are not lost as you digitize individual glyphs. If you are comfortable taking creative liberties, you might exaggerate those points of differentiation.
In the font Marydale, Brian Willson included quirky glyphs such as the loopy, two-story serif g found in the original handwriting. Brian thought it added friendly charm. But a risk of embracing idiosyncrasies is that some users may not like those letterforms — and indeed some users complained to Brian that the g was too unusual. Nevertheless, Marydale is one of Brian’s best-selling fonts.
To help you decode shapes and understand the mechanics behind some of your specimen’s idiosyncrasies, it may be helpful to identify the type of writing utensil that was used. A ballpoint pen will create uniform-width strokes; a split-nib pen can create graceful thicks and thins; a dip pen may carry evidence of ink being reapplied; a brush can create dramatic variation in thickness. You may also consider how the writing utensil had been held or if the writer had been in a hurry, since hand position and speed can influence the shape and style of the handwriting, too.
Assess Variations In Axis, Letter Height, Alignment To Baseline, And Stroke Width
Within any single handwriting specimen, you will likely see variation in axis, letter height, alignment to baseline, and stroke width. These irregularities enhance the individuality of handwriting — but transferring those irregularities to a digital font can be a challenge. If your font includes too many variations in axis, letter height, alignment to baseline, or stroke width, it may not reflect the visual unity of the original specimen. If your font does not include enough variation, it may lack the charm of the original writing.
Take time to assess which elements in the original specimen are consistent and which vary, then plan how you could incorporate those variations into your font. If you employ a consistent axis, consider varying alignment to baseline or stroke width. If you standardize stroke widths, consider varying axes and letter heights.
Fonts with increasing variability in axes, letter heights, alignment to baseline, and stroke thicknesses. (From left to right: Texas Hero by Brian Willson/Three Islands Press, P22 Cezanne Pro by Michael Want and James Grieshaber, and Selima by Jroh.) (Large preview)
When I began working on the P22 Marcel Script, I sourced favorite individual letters from five separate handwritten pages. The first time I pieced glyphs into words, I could see that the axes, letter heights, and stroke thicknesses were too variable. Even though each glyph had been a careful re-creation of one man’s handwriting, the resulting look was haphazard. I decided on a standard for axis and stroke thickness, then adjusted every glyph to that standard. Varying letter heights and alignment to baseline prevented the font from looking too mechanical.
Where And How Do Individual Letters Connect?
Are the connecting lines that sweep from one letter to the next high or low? Are the connecting lines thick or thin? Are there some letters that do not connect?
The key to designing a successfully connected cursive script font is to create an overlap so individual letters appear to seamlessly flow from one into the next. The trick is to identify one location for that overlap, then to start and end every glyph in that precise position. Some designers place those overlaps along the left-hand edge of a glyph, other designers place the overlap in the space between the glyphs. Some designers place the overlap low, others place the overlap high. There is no right or wrong answer; choose the location and method that makes sense for you.
Fonts that overlap on the left-hand edge of each glyph. The magenta circle shows the overlap; the horizontal magenta line shows the consistent placement of the sweeping connecting line on the letters m and a. (From left to right: Douglass Pen by Brian Willson/Three Islands Press, Rough Love by Positype, and Bambusa Pro by FontForecast.) (Large preview)
Fonts that overlap in the space between the glyphs. (From left to right: Dear Sarah Pro by Christian Robertson, Storyteller Script by My Creative Land, and Mila by Facetime.) (Large preview)
You may also discover that not all letters in your specimen connect. In that case, you will still need to identify one location and implement a consistent strategy for the overlaps, though you will only create the overlap on those glyphs that connect.* *
Fonts where some, but not all, letters connect. (From left to right: Blooms by DearType, JoeHand 2 by JOEBOB Graphics, and Brush Marker by Fenotype.) (Large preview)
Do You Want Your Font To Include A Texture Effect?
Adding texture can enhance a feeling of antiquity or nostalgia, but this treatment adds time, complexity, and increases file size. And it may influence whether or not someone will want to license and use your font, since they may or may not be looking for that specific effect.
Examine your original specimen to determine where the texture came from. Was it caused by the paper surface? Was variation caused by the writing tool or writing speed? Are irregularities clustered on curves? Does one side of the letter include more texture than the other? Do brush strokes or splatters of ink extend into the space surrounding each letter?
The word “Smashing” typeset in three different fonts that have three different textured looks: a paper texture look, rough edges, and brush strokes. (From left to right: Paper texture; Thirsty Rough by Yellow Design Studio, Scratchy pen and ink texture, Azalea Rough by Laura Worthington; Brush strokes, Sveglia by Wacaksara Co.) (Large preview)
Once you’ve made high-level decisions on the importance of historical accuracy, identified what idiosyncrasies make the handwriting special, considered how to add variation in axis, letter height, alignment to baseline, or stroke width, assessed how to connect glyphs, and decided if you want to include texture, it’s time to forge ahead with the design of your font.
Pick Your Letters
Make a copy of your specimen, and go through it line by line, flagging the specific characters you want to incorporate into your font. When designer Brian Willson begins a new font, it is not unusual for him to spend hours poring over the source material to select the individual letters he plans to include.
Consider flagging two types of letters:
Favorite “workhorse” letters
Workhorse letters will make up your basic glyph set. They might not be the fanciest option, but workhorse characters will make for a reliable and legible glyph set.
Favorite swash letters
Swash letters might include extra loops or flourishes, more or less texture, greater variation in position above or below the baseline, or exaggerated features such as extra-long crossbars.
Swash letters may be less legible, or may only be appropriate for use in specific instances, but can add variety, beauty, and personality. (Swash glyphs will be added later in the process; focus first on designing the workhorse glyph set.)
Ideally, at the end of your review, you will have flagged a complete set of upper and lowercase workhorse letters, numbers, punctuation marks, and an array of swash characters.
Scan Individual Letters And Prepare To Vectorize
Create high-resolution bitmap images of all letters you plan to include in your font. I have always used a flatbed scanner to capture those images, but I have heard of people exporting sketches from Procreate or taking high-resolution photos on their phones. If you take a photo, be sure your phone is parallel to your specimen to avoid distortion.
Once you have assembled a collection of bitmap images, you will need to choose between vectorizing the letterforms using Adobe Illustrator’s Image Trace feature or importing the scans directly into font editing software then vectorizing them by hand.
Using Illustrator’s Image Trace feature may be preferred if your specimen includes lots of texture since Illustrator can capture that texture for you. To create a vector outline, import a bitmap image into Illustrator, then using advanced Image Trace menu options, test combinations of Paths, Corners, and Noise to get the tracing result you prefer. Expand to get your vector outline.
Importing scans directly into font editing software may be preferred if your font is not going to include texture, if you are comfortable generating Bezier lines, or if you intend to make significant revisions to letter shapes.
Popular font editing software options include FontLab Studio VI (Mac or Windows OS, $459), Glyphs (Mac OS, €249.90), Robofont (Mac OS, $490), Font Creator (Windows, $79–199), and Font Forge (free). There is also an extension for Illustrator and Photoshop CC called FontSelf which allows you to convert lettering into a font ($49–$98).
Establish a new font file. If you created vector outlines using Illustrator, import each outline into the applicable glyph cell (that is, place the vector outline of the letter a in the a glyph cell so that an a appears when you hit the a key on your keyboard).
If you chose to vectorize the glyphs within the font editing software, import each bitmap scan as a background sketch, then trace.
Identify Archetype Letters
Some font designers begin by designing or refining the letters n, b, o, v, A, H and O since those letters contain clues to the shape of many other letters. The n, for example, holds clues to the shape of the i and h; the b holds clues to the shape of d, p, and q; the O holds clues to C and G. Other designers begin with the most frequently used letters of the alphabet: e, t, a, o, i, and n. In the fantastic book Designing Type, which is chock full of images that compare variations in letter shapes, Karen Cheng notes some designers begin with the letters a, e, g, n, and o.
You may begin with one of those glyph sets, but if you’ve identified a few letters that quintessentially represent the aesthetic of your font, consider starting by refining those glyphs. When I began to work on the P22 Marcel Script, I began by working on the capital M for no other reason than the swoop of the original handwritten M was exquisite, and it brought joy to see that letter come to life as a glyph. (After working on the M, I focused on refining archetype letters n and e.)
From left to right: Original scan, original tracing, glyph outline in FontLab (note the refinements to stroke width and axis), and final M in P22 Marcel Script. (Large preview)
No matter which glyphs you begin with, you will quickly want to establish standards for the axis, letter heights, alignment to baseline, and stroke widths. Ensure all glyphs meet those standards while simultaneously keeping in mind incorporating variability to achieve an organic look.
In order to introduce variability in an intentional way, it might be helpful to add guidelines to your workspace to define the lower and upper ranges of variability. Use these guidelines to introduce variation in ascender height, descender length, or in alignment to the baseline.
Do you want your font to have more variability? Increase the distance between the guidelines. Do you want your font to have less variability? Decrease the distance between the guidelines. Add variability to stroke widths in a similar way.
Lowercase glyph set of P22 Marcel Script. Notice variability in length of the descenders in the letters f, g, y, and z, variability in the height of letters i and j, and how I chose not to connect letters v and w with the following letter. (Large preview)
You will also want to establish a standard position for the overlap. Since there is not one correct place for these overlaps, experiment with a limited number of glyphs until the overlaps appear as natural as if the letters had been written with a pen without lifting the pen off the page. Then, test the position of the overlap on tricky letters such as r, o, s, f, v and w to confirm the overlap works. You’ll know if there are issues because glyphs won’t connect, or the connection won’t appear smooth. (If you see white where two black letters overlap, check for a Postscript path direction error.)
The good news is that once you have established the successful position for your overlaps you should be able to cut, copy, and paste the connecting line to replicate the position of the overlap on all remaining letters.
As soon as you have a collection of glyphs — even if it isn’t the entire alphabet — generate a test file and preview your font. If your goal was to replicate your specimen with accuracy, assess whether the font reflects the rhythm and character of the original handwriting. Evaluate whether the “color” — the overall darkness or lightness — matches the original. Refine glyphs as necessary to achieve the right rhythm, character, and color.
If you have chosen to take liberties with the shapes of letters or to introduce variability, your goal should still be to achieve an overall cohesive aesthetic. It is up to you as the designer to define precisely what that means.
As you test, it will be helpful to print blocks of sample text at a range of sizes. Reverse letters out of black. Look at the spaces between letters. Look at the spaces inside of letters. Look at strings of glyphs backwards, then upside down. Look at the font both when printed on paper and on a computer monitor. Testing under different conditions will help you notice glyphs that need additional refinement. I found that when I tested sample text set in a foreign language the unfamiliar letter combinations would help me see individual glyphs that were too heavy, too light, too narrow or too wide, along with individual curves that seemed too rounded or too flat.
For the first-time type designer, I recommend the book Inside Paragraphs: Typographic Fundamentals by Cyrus Highsmith (see list of references below). The book provides an invaluable primer on learning how to look at shapes and spaces in and around letters.
Continue testing and revising glyphs until your font includes a–z lowercase, A–Z uppercase, numbers, fractions, punctuation marks, and diacritics (marks such as the umlaut, acute, or tilde added to letters to indicate stress or change in pronunciation).
Add Swashes And Alternate Characters
Once your workhorse glyph set is complete, consider adding the swash characters you flagged when you picked your initial letters. A digital font can never offer the infinite variability found in handwriting, but by writing lines of OpenType code and incorporating swashes, ligatures (two or more letters that are combined into a single glyph), and alternate characters, you can begin to close the gap between the mechanical nature of a font and organic variation of handwriting. OpenType code allows you to do things like ensure two of the same glyphs never appear next to each other, or replace a workhorses glyph with a fancy swash or with a glyph that has more (or less) texture.
This work can be time-consuming, but you just might find it is addictive. You might discover every word you test could benefit from some custom flourish. The font Suomi, designed by Tomi Haaparanta, includes more than 700 ligatures. The font Hipster Script, designed by Ale Paul has 1,066. Syys Script by Julia Sysmäläinen for Art. Lebedev Studio has more than 2,000 glyphs. And between the Latin and Cyrillic versions, the font NIVEA Care Type by Juliasys has more than 4,000 glyphs.
Between swashes, ornaments, and alternate characters, the Pro version of the P22 Marcel Script includes more than 1,300 glyphs. Many of the alternate glyphs were inspired by flourishes in the original specimen; other glyphs were of my own invention, but were made in the style of the original writing. In my experience, incorporating swashes, ligatures, and alternate characters is the most exciting part about designing a connected cursive script font. In fact, it is what brings a connected cursive script font to life.
From left to right: P22 Marcel Script basic (workhorse) glyph set, P22 Marcel Script Pro incorporating alternate glyphs, and P22 Marcel Script Pro showing additional alternate glyphs, swashes, and an ornament. (Large preview)
Final Steps
Once all your glyphs have been designed and the font has been thoroughly tested for technical performance and aesthetics, it is time to name the font and release it into the world.
Ensure no other font is already using the name you are considering. You can do a preliminary search on aggregator websites such as MyFonts, Fonts.com, FontShop, or Creative Market. Fonts are also distributed by individual font foundries and designers. Because there are so many distribution channels, the only way to guarantee availability and protect a name is to apply for a copyright with the U.S. Patent and Trademark Office (for U.S. designers). Consider hiring a lawyer to help with the filing process.
Finally, when it is time to release the font, if this is your first font it may be easiest to distribute your font through an established foundry or aggregator website. They should offer technical support, and will track licensing and sales tax. Consider working with one of the websites listed above; each website will have a different process to submit a font for consideration.
I’m a big fan of templates. They’re an essential part of any productive designer’s workflow and they can also be really helpful in boosting your brand’s identity through greater consistency and professionalism.
Project management templates are one such example of templates that definitely belong in the workflow of a web designer. They free you from having to handle tedious project and time management tasks and, consequently, enable you to focus more on your work.
I also really like design snippets, mockups, and themes (like the ones you might find in WDD’s Freebies). When you use the same kinds of design elements over and over, it makes sense to have a reliable base of templates to pull from.
What I’m not particularly fond of—and you shouldn’t be either—are design templates that remove all creativity and strategy from your process.
The reason I bring this up is because I believe there is a right way to use templates in web design, and a wrong way.
The Wrong Way to Use Templates in Web Design
I recently attended a WordCamp event to learn more about what’s happening in WordPress and, specifically, what sort of trends I should be on the lookout for in web design.
There was one session I attended called, “Fast Track Your Design Process”. I was excited for it as I’m all about productivity hacks that help you work less while accomplishing more.
However, I walked away from the session incredibly disappointed by what I heard; the speaker told the room of about 50 designers that they should be building client websites using templates. That suggestion I had no issue with. I believe that WordPress themes are a great time-saver for many web designers—especially if they’re looking to start with a strong, responsive base that they can customize.
The problem, however, was in the rest of the advice given. It basically went like this:
1. Find a theme that fits your target niche nicely
For instance, if you design websites for real estate companies, purchase a license for a real estate theme.
Make sure you’re very familiar with the theme you choose.
2. Identify the key pages that your typical client needs on their website
Home, About, and Contact pages are a given.
Similar to how a theme developer might provide a few page layouts or design options to choose from, you would do the same. You could use the templates from your theme or design your own. Then, create no more than two or three template options for each page.
3. Save all your templates for future use
These are for no one to see but you.
4. Sign a new client to your web design services
Explain to the client that you are going to build the perfect website for their business. Have them sign the contract and provide you with any branding information or images you need to use.
Then, use this limited set of templates to build every website you’re hired to create. You still have to choose which of the page template options make sense for each client and add personalized content to the pages. But that’s about it.
The goal here is to make as much money as possible from each job.
5. Don’t engage with the client about the web design
Someone asked the speaker how he explained to clients that he was using templates to build their websites. (Which is a valid concern.)
To this, he told them that the client didn’t need to be involved in the process. Web designers are the experts and they know best, so clients shouldn’t have any say in what goes into the website and don’t need to see it until the work is complete.
My Two Cents on the Matter
At that point in the lecture, I raised my hand and presented a number of objections:
It’s a terrible practice to not involve the client in their website project. It can lead to costly rework and also has the potential to hurt your business through negative reviews; people love to talk about bad experiences.
When you limit web design to a few templated options, you run the risk of creating lookalike websites—especially if you take a niche approach to your business. This could hurt your business if clients start to notice that you’re not putting any time or effort into the work. Also, how can you expect to build an impressive portfolio if every website looks the same?
This could also hurt your clients’ businesses if their visitors realize there’s nothing unique to the site since it’s just a copycat of another.
My input was not well-received, but I’m hoping you can appreciate the logic here.
The Right Way to Use Templates in Web Design
I’m not opposed to using templates in web design. Heck, I think that if you don’t use templates in your business, you’re making a huge mistake.
I understand the desire to want to cut corners so you can make a higher markup on your website projects. We all want to make more money. But I don’t believe that removing all strategy and creativity, and providing clients with a canned website is the solution. There are other ways to make use of templates and boost profits in the process.
Themes
Themes that skin your entire website are a great option. You still have to customize them and work on crafting search-optimized content (written and visual) for the website. But they’ll save you a lot of time.
Sectionals
Sectional templates are a good way to quickly replicate the same design elements across a website.
Another way to use them to your advantage is by turning them into wireframes. Use the bare bones of a sectional template to quickly bootstrap the structure of a page on another website.
WordPress Template
There is a multi-site management tool called ManageWP that comes with a WordPress Template Builder.
If you build websites with WordPress, you can use this to save time with new installations. Simply create your template WordPress install and add the plugins (and themes) you use often on your client websites.
Project Management Checklists and Templates
Perhaps the best way to save yourself time is to simplify as much of your project management work as possible. (That’s the part of running a freelance design business that you dislike the most anyway, right?)
Create checklists for all of your web design and business processes. Develop templates for communications you send to clients, contracts for new projects, and instructions you provide to freelancers or other team members. Even templatize your invoicing.
Summary
There is a difference between adopting templates for the purposes of optimizing your design workflow and adopting them so you can avoid doing any real work. I think this is what leads to the delivery of poor websites and gives clients a way to talk designers down in price.
You are a web designer and you’re being paid to provide a creative service. While 100% of the elements you put on a website don’t need to be handcrafted by you, you can’t expect clients to pay you well if no thought or consideration was put into the development of their design.