Not a typical one, at least. Each character is an HTML element, built with CSS. A true web font!
Let me elaborate. This is a way to render text without using any font at all. Random text is split with PHP into words and letters, then rendered as HTML elements with classes. Every element is styled with CSS to create the characters. This is “just” HTML controlled with CSS, but still, it is software and it gets the message through. It has all the properties a conventional font does, so we’ll call it a font. A font without a format.
Disclaimer: I’m not an expert in HTML, CSS, or PHP. I’m willing to bet there is a shortcut or an easier solution to achieve what I’ve done here, but since I’m happy with the results, I will present the process and my experience. The presentation is not a tutorial; it is an experiment based on my limited skills and should be treated as such.
The idea
The project was never meant to last for five months, but that’s what it took! It all started with having a play with a CSS icon, using pseudo-elements to make shapes. Once the first S letter was finished, the rest were relatively easy. I checked to see if there were other similar projects but didn’t find much, so I was motivated to see how far I could get.
Initially, an SVG font controlled with CSS seemed like a good idea. It would make this task much easier (SVG is made for drawing) and could focus on design-specific effects, but it doesn’t have the flexibility of a raw HTML element. An SVG cannot be modified depending on context, and the process falls back to the conventional font design, where every character has a fixed shape and code.
How it works
This is a hybrid of web and font design. Each character is built like any web element and used inline to behave like a font. Metrics, Weights, OpenType Features and all the other font properties are controlled exclusively with the CSS file.
The font design is based on the border width of the elements, which makes it extremely versatile. With the exemption of script fonts, several styles and weights can result just from border variations, using the same shape. On more complex characters the clip-path and the background is used to create the cutout effect.
Nested elements are generated when the ::before
and ::after
pseudo-element is not enough to form a character. Using em values for width, height and border widths will help later in controlling the font size. This is one of the golden rules.
A character (left) is built like any CSS icon (right). There are no major differences. Sometimes a letter is easier to build, just like a stickman, based on circles and lines. But here is when you can really appreciate the role of the border-radius
property. Personally, I never was a fan of rounded borders, but this experience changed my mind. Basically, there’s no limit for what a radius can do.
Below are the only two “real” examples of the CSS font in this article, the rest of the example figures are converted to SVG for easier display in a blog post.
2. Red – pseudo elements
3. Blue – Extra element
The serif preview presents a more complex situation, but as usual, a sans font will have fewer elements to deal with, making the file is smaller and load faster. This is not really an issue and it’s only logical — the CSS is read before a font embedded with the @font-face
rule.
The challenge
The hardest part is to beat the pixel ratio, or align the pseudo elements to the base shape. Elaborated mathematical formulas failed when the charater was resized. A browser will treat each element separately and shift them to the closest integer value.
A solution to this was to create as many pseudo-elements as possible (even including extra elements), and use a single reference for a pair of ::before
and ::after,
not related to the main shape. In this case, the browser will render the elements more or less to the same position.
A character without a point of reference is illustrated with the S letter below. The top and bottom section of the letter are two pseudo elements, without a base shape to rely on (e.g. the gray area in the serif above or here in the digit two).
After creating a few hundred characters, you realize that a character cannot support inline transformation (i.e. skew()
, rotate()
, and such) because it won’t align to siblings. This becomes more obvious visually on text selection. Because of that, a pseudo-element makes perfect sense. I would say essential: the second golden rule.
clip-path
is used.clip-path
.CSS custom properties
It seems easier to create a style in CSS than it is in font softwares. You have the option to control shapes and sizes for multiple characters at once. In CSS, more characters are grouped together in the same ruleset.
CSS custom properties are extremely handy in this situation, especially for controlling borders, widths, and positions. The different weights are the result of changes in the variable, with adjustments afterwards. Fine tuning is unavoidable because character shapes and sizes take the border width into account and may not display proportionally with different borders, especially on asymmetrical shapes.
The cutout effect is created by adding the same background color to the overlaying element, then using a combination of colors and effects using mix-blend-mode
.
A global color variable is required in CSS to create a cutout effect for nested elements that otherwise would follow the parent color (overlaying elements match the background).
The background-image
property won’t work on characters built exclusively with borders and the background is changed if the element has size or position transformations (scale, rotate, or other).
Where a background cannot be used, the solution is mix-blend-mode: lighten;
on dark backgrounds and mix-blend-mode: darken;
on light backgrounds.
Side effects
The downside is that some effects can have unexpected or even opposite results on elements with variable properties. Usually, a filter
will read elements as full objects. To prevent any conflict, borders and transformation effects are reserved for the font design.
Font to text
A font won’t make a text. The idea in the first place was to create a text that will load along with the CSS, without any dependencies. For that the best option is PHP (my rookie opinion). Besides rendering HTML with inline functions, it is up to almost any task imaginable. Without PHP this project would not be possible.
Naturally, the first task with PHP was to split a random text, remove extra spaces and create matching groups for every word and letter, each one with its own class. So far, so good. I won’t insist on the part that went smoothly, it is a basic function, using split, explode and all the other words borrowed from a video game.
Still, since I never worked on this before, I had to learn the hard way. Nobody told me that PHP considers the “0” (zero) as null, so there’s a day gone. I couldn’t figure out why my zeros are not displayed.
For anyone with this issue maybe it’s helpful. Instead of using the empty()
function, I used the one below:
function is_blank( $value ) {
return empty( $value ) && !is_numeric( $value );
}
The other major issue was the character range. It seems that there are way too many settings in HTML, the .htaccess
file, and on the server itself just to recognize special characters. The solution was found after a few days in the PHP Documentation, posted by qeremy [atta] gmail [dotta] com, obviously somebody living in a diacritic-heavy area.
function str_split_unicode( $str, $length = 1 ) {
$tmp = preg_split( '~~u', $str, -1, PREG_SPLIT_NO_EMPTY );
if ( $length > 1 ) {
$chunks = array_chunk( $tmp, $length );
foreach ( $chunks as $i => $chunk ) {
$chunks[$i] = join( '', ( array ) $chunk );
}
$tmp = $chunks;
}
return $tmp;
}
A lot of chunks, if you ask me, but it works like a charm and solves every issue. The function basically overlooks the language settings and will read any character, even the non-standard ones. Characters buried deep in the Unicode tables will be recognized if the PHP function includes that character.
This function will only create the possibility to generate each character as typed, without the need for HTML entities. This option won’t limit the use of text in HTML format, but inline codes must be avoided or replaced with alternatives. For example, instead of using non-breaking spaces (
), elements can be wrapped in the <nobr>
tag.
Default system font on the left. On the right is CSS text rendered without any changes.
Structure
With this solved, the next step is to create a specific structure for each character. The class of the HTML elements and the positions of the nested element depends on a long list of characters that correspond with one or more classes. Some of the most basic characters are not excluded from this list (e.g. the small “a” letter needs a finial and that means an extra element/class).
The basic structure looks something like this, just to get the idea …
'Ć' => 'Cacute C acute'
…which will render three elements: the parent Cacute, the C letter, and the acute accent.The result is below, where the red square represents the parent element, containing the other two preset elements.
The technique is very similar to the way diacritics (sometimes ligatures) are built in font software, based on pairings. When a component element is changed, every other will adjust.
Because any element can have multiple applications, the IDs are avoided and only classes are used.
OpenType features
The PHP function is set to behave differently depending on context. The character recognition is set to replace pairings and create ligatures when rendering the CSS text.
Contextual ligatures in the CSS text are not standalone characters and don’t have specific classes. Different from conventional OpenType features, the characters are restyled, not replaced. The interaction is controlled in CSS by styling the second element, to merge or form a new character.
The features are activated with a specific class added to the parent container. Alternates are rendered in any circumstance, regardless if a character is registered or not, in every browser, with or without font feature support.
.ordn
class. The characters are styled inline and change their look according to class and their previous sibling. The same recipe is applied for Stylistic Alternates (salt
), Oldstyle Figures (onum
), Slashed Zeros (slsh
), Superscripts (sups
), Subscripts (subs
) and Fractions (frac
).Classes with similar functions to OpenType are named after the Registered Features by Microsoft/Adobe.
HTML syntax
Any HTML element can include the CSS font, as long it has the .css
class next to the weight of the font. To select a weight, the .thin
, .light
, .regular
or .bold
class is used, something like <pre class="regular css">
(the <pre>
tag is just a safety measure to avoid any style interference).
The text can have an HTML format. The plain text is not mandatory.
PHP will ignore a bracket (<
) if this has a closing correspondent, which means that every HTML tag in a text will remain active and only the text content is rendered as the CSS font. URLs, file paths, or other additional info found in the tag are encoded just the same by the browser. The same tag can style groups of letters or entire sentences, if they’re set in CSS.
Also, depending on layout preferences, specific tags — like <a>
, <u>
, <ins>
, and <del>
— can be treated as objects to emulate and customize their native appearance and behavior.
Setup
CSS text is a group of objects with borders, open for size and color treatments. Think color as border-color
and vice-versa. :first-child
instead of :first-letter
.
The font-size
is set in the CSS file, the same as any other font, using viewport, percentage, pixels, em or rem units. Values set in pixels work with decimal values.
The text-align
and text-indent
properties work by default. The text will align to any setup even without text content.
Block-level elements (e.g. <div>
, <p>
, <ol>
) placed inside texts will cause a line break, as it would normally. The <br>
tag works as expected.
Except for text formatting elements (e.g. <h1>
–<h6>
, <strong>
, <em>
, <small>
, <sup>
, <sub>
, etc.) that will need new rules to have the right effect on the text, most of the semantic elements (e.g. <form>
, <ol>
, <li>
) work with their custom settings.
The font
To test the font in dynamic content, part of the PHP function was reproduced in JavaScript, with paste, mouse events, caret positions, and text selection. A single keystroke now makes it all worthwhile.
The CSS font and the complementary icons. This is what actually started the whole thing!
Review! Pluses (+) vs. Minuses (-)
Instant load
In the absence of actual text, the browser doesn’t wait for a font and a script to render the page. The CSS file along with HTML elements are cached, which means faster loads.
Universal
Every browser and server recognizes CSS. Fewer worries to find the right format that works the same in every browser. A server will not check for a specific format to allow access.
No dependencies
The CSS font doesn’t need alternate or system fonts to display the text. The same CSS that styles the page can include the font. The browser will not display a default font, neither before nor after the page load. The font does not rely on third parties and scripts, and the design is not different on browsers with disabled scripts.
No embedding
The CSS font is fully integrated into a webpage, and adapts to the layout without replacing other elements on load. Every page property is automatically valid for the text and this will show up the way it was intended, without after effects or functional issues.
Selective use
The font can be reduced to a limited number of characters. The full version is not required if the layout has a single word or a symbol, for example.
Full security
The actual text is not present on the page, which means that sensitive informations can be easily displayed without the fear of spam or phishing.
SEO friendly
Important information can be included using tag properties, the same way the alt attribute works for images.
Customizable
To build complex characters or functions, the font is open for any HTML element. No need for scripts to get specific details because every word and letter has its own entity and can be styled individually.
Contextual
The font design is not limited to predefined characters, so the style can change depending on context, without creating new characters.
Consistent
To compensate for the lack of automation found in font softwares, in CSS, the design can control several elements at once. This argument is valid, since a font software works with existing content, while CSS works with properties, creating a template for every existing or future elements.
Public
Anyone can create their own font. Short texts can be rendered manually, and the PHP function is not a requirement.
Basic
The design is accessible with any text editor or developer tool. Elementary skills using border widths, border radius, shapes and sizes is enough to redesign any character.
Live
Every adjustment result is instant. Conversions, exports, uploads or other steps to activate the font are eliminated from the process.
Moderate use
The speed of the page may suffer if a CSS font is generated for extended texts. This technique is only recommended for headlines, titles, excerpts and short paragraphs for that reason.
Common
The CSS font will not benefit from special treatments because, to the browser, this is just another HTML element. As a result, there’s no optimization or kerning support. The pixels have a hard time sharing thin lines and at small sizes the font may display improperly.
Hard coded
Your usual font settings are unavailable by default and styling tags (e.g. <strong>
, <em>
, etc.) have no effect. The functions must be set in the CSS file and require a different approach, working with HTML elements instead of fonts.
Exclusive
This is a webfont, so it is limited to digital media controlled with CSS. Except for some bitmap effects, the font can only be translated for offline by printing the document as a PDF, which will convert the CSS into a vector format.
Abstract
Without a standalone file the font is hard to be identified, tested, or transferred. It works like the HTML color: it’s invisible until it’s generated.
Not selectable
Without extra scripts, the text cannot be selected or used in inputs and textareas. For dynamic content, the function needs the whole character recognition found in PHP.
Not interactive
The most common display functions, such as sort
or filter
, will have to work with classes, and not with text content.
Not printable
The online print supports only basic CSS rules, sometimes ignoring graphics in favor of texts. The print quality will rely strictly on the browser’s capabilities.
No accessibility
The CSS font will adjust to page zoom, but the font size and the languages cannot be changed through the browser.
Custom browser functions (e.g. Find, Reader) cannot access the text content since there is none.
Limited design
There is no wide selection of styles to choose from and the design is limited to the capabilities of CSS. A CSS rule can have different meanings to different browsers, causing inconsistencies. A CSS font is written, not drawn, so the “hand-made” concept is eliminated completely.
Multitask
You need to know your CSS to make adjustments in the font, and vice versa. The design process is not automated, and some properties that are otherwise generated by a machine must be set manually.
No protection
The design code is accessible to anyone, the same as any online element. The design cannot be really protected from unauthorized copying and usage.
Thanks for reading! Here’s the fonts homepage.
Not selectable – This is the killer for us
You could create a function and process to maybe allow that with js? That’d be interesting.
Love this project, very cool
It’s a neat idea but I don’t expect this to ever catch on, since a screen reader wouldn’t even be able to recognize it as text.
I could see somebody using this with css transitions for creating motion graphics. Another application might be a novelty site where a user inputs some text, adjust some settings, hits a button, and it gives them the html and css they can then do whatever with
Uhm. But why?
I don’t see how this has any real benefits other than the font being immediately available with the CSS file.
You know you can base64 encode your font and inline it in the CSS file, right?
Interesting, and believe me I’ve done plenty of experiments just to see if they could be done so I enjoyed reading this and applaud the experiment, but there are so many issues here in the things you list as “pros”.
Accessibility was the first thing that came to mind when I read the title of the post.
I also have serious doubts about whether search engines will actually index the attributes you mention—-you say they’re similar to img alt tags, but that doesn’t mean they’ll be read.
And while yes it’s “cacheable”, it’s not cacheable independently from the page content. At least with fonts you cache the font file then reuse that on second and subsequent page views. This has to download the complete data for everything on every page view doesn’t it?
Plus if I’m not mistaken the code this generates is actually larger than the font data it would be replacing. The amount of data needed to represent a single character in the markup is far larger than a regular character, and that has to be repeated for each instance of that character in the page. And then you have all of the associated CSS on top of that.
Again technically interesting and thanks for sharing, but I think this might need a “not suitable for production use” disclaimer at the top.
“No accessibility” should be much higher up in the list of cons for a project like this. It’s a really important consideration that often gets treated as just another “nice to have.” This is a really cool project from a technical-experimental standpoint; I’m honestly amazed at the creative application of styles that was required to make the characters not only legible, but legitimately attractive and cohesive as a font. But let’s be clear: no one should ever use a CSS-based font in production for any reason.
This reminds me a little bit of sIFR which, for those of you who aren’t older than dirt, was a way to replace text with an Adobe Flash object because back in the day we didn’t have Google Fonts and Font Squirrel and all the other really great web font resources.
sIFR took important page content (ex. a header title) and turned it into something that was no longer selectable, searchable, able to be copied, etc. Essentially it took content (and context) away from the page and made it less accessible to the user. It turned something informative into something decorative.
… as a proof of concept this is brilliant. As an example of the flexibility of CSS this is amazing! But as something to deploy on an actual website? That’s the point where I hesitate, because it does feel like a step backward in terms of user experience and accessibility.
I would also point out that some of the pros don’t feel as relevant as they might have even a few years ago.
@font-face
enjoys wide support, and the number of web-friendly font resources has exploded in recent years. And with so many prominent voices in the web development world reiterating the importance of performance, font creators have also begun focusing on providing us the most performant fonts they can generate.Nice read.
Quirky projects like this is often (initially, anyway) fun, and you end up learning a ton of stuff.
This is overly simplified. PHP does not, generally, consider 0 as null. The particular function you used,
empty()
, is used for testing falsy values, which includes zero.You’re probably right. I guess you just want to split a (multibyte/utf-xx/unicode) string into an array of single characters, so you could get the job done with something like this
(it’s essentially what qeremys code does, sans the option for grouping multiple characters)
Thank you all for your comment.
I appreciate this especially, because I can learn something.
Otherwise the project is indeed for fun, not to start a new cause =)
very interesting exercise, perhaps it’ll be useful as wordart generator, or logo generator? as you mentioned, not suitable for block of text but fun to use for headers
Other CSS fonts made previously.
– 2004 http://www.cssplay.co.uk/menu/cssfont
– 2010 https://web.archive.org/web/20100414205534/https://desandro.com/resources/curtis-css-typeface
– 2015 https://yusugomori.com/projects/css-sans/fonts
Thank you! It’s good to know I can share the critics with other people. The one made in 2015 is similar to my approach. Great!