Skip to content


Switch branches/tags

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time


Makeanico is a progressively enhanced web app that allows anyone to create a 16x16 favicon.ico graphic. You can access it by waking the sleeping dino.

10K Apart

You can vote for this entry here but first you should take a look around the gallery. There are many amazing submissions to look at.

Makeanico was originally created as a submission for the 2016 10K Apart competition.

The "base" functionality of Makeanico is the ability to create a 16x16 favicon and export it in .ico, .svg and .png formats. This base functionality is achieved with an initial load of less than 10kB. Approximately 2kB of CSS styles are used to graphically enhance the experience for sighted users. This is done by styling a semantic HTML <table> of <input type="checkbox"> elements to create a "WYSIWYG" graphics editor.

For example, here is the 10K Apart Favicon as represented by the Makeanico WYSIWYG Editor:

10kapart favicon loaded into Makeanico art–board

Without the WYSIYG CSS styles we lose that "what you see is what you get" feature but powered by semantic HTML the raw experience is as functional with or without styles.

Weigh In

Run npm start to run the server in production mode with GZIP and minification enabled. Host over https.

In accordance with the Rules & Regulations of the competition Makeanico keeps initial page weight under 10kB. I use the Chrome Developer Tools to get an idea of the page weight. Safari and Firefox seem to display uncompressed file sizes even though GZIP compressions is in fact being used.

Name Size (GZIP)
index.html 5.7kB
main.min.css 2.3kB

That's the initial load. If you include init.min.js which is lazy-loaded right off the bat for users whose scripts can cut the mustard then we're at:

Name Size (GZIP)
index.html 5.7kB
main.min.css 2.3kB
init.min.js 1.7kB

Note: Sizes include header weight

Again, it is my understanding that given that the init script is only loaded as needed (if it passes the doEnhancments test) than in doesn't count. But I want to demonstrate that even if it does I calculate the weigh in at under 10kB.

  var doEnhancments = document.addEventListener ? true : false;
  if(doEnhancments) document.write('<script id="scripts__init" src="/assets/js/init.min.js"><\/script>');

The favicon.png, which is not included in the weight, weighs 266 bytes in Finder. So even if you count that it is still under 10kB! The service worker, which is loaded if 'serviceWorker' in navigator weighs 324 bytes GZIPed. I don't think the weight of lazily loaded service workers counts though…

Once a user begins interacting with the art–board or choosing a fill color enhancements are loaded as needed. These include the:

  • live preview
  • export
  • keyboard shortcut
  • swatches
  • accessibility preferences

Some Enhancements are fetched for "dirty" art–boards. If there is art on the canvas, editing enhancements are loaded. Since the app has previously been visited, these assets are likely loaded from cache by the browser cache or the service worker. Any dirty artboard is not the initial page load so it is ok for these pages to creep above 10kB.

The more pixels you draw, the longer your URL gets, and the larger the size of the index.html gets too! That's ok though because only the "blank canvas" homepage counts as the initial load. Pretty much everything except the HTML source is going to be served by the browser cache if possible. Even the HTML source itself of return visits is cached offline by the service worker. This means that not only can page visits weight 0.0kB but also that JavaScipt users performing asynchronous actions that don't need to communicate with the server (import, export, post) can enjoy an offline editing experience.

The larger initial page weights of lazy–URLs I have counted to dirty art–boards are over 10kB. That's with the service worker and browser cache off and includes the initial weight plus all the lazy–loaded enhancements. To reproduce this visit a dirty art–board with every color filled.

You can also reproduce this by importing a photo with edge–to–edge to color. From a mobile device snap a photo to import your art to the art–board.

The Accessibility Preferences are lazy loaded in localStorage capable environments. They lazily add about 730 bytes of CSS and 1.6kB of scripts if localStorage dictates they should be loaded. For example, if the Font Size preference is changed from the default value, a little CSS and some scripts will lazily be loaded to update the user interface is response to user preferences. You can also choose a typeface like OpenDyslexic and Fira. Of course the web fonts are heavy and lazily loaded only if requested by the user.

Color the Browser

Browsers that support the theme-color, msapplication-navbutton-color and apple-mobile-web-app-status-bar-style tag(s) will visually convey the selected color within the browser interface.

Try it yourself in a browser like Vivaldi.

History Support

History is supported in both the base and enhanced experiences. The HTML5 History API is used to leverage Undo, Save, Bookmark, and Share features by pushing the graphic state to the URL. As you update your favicon the URL is updated accordingly by pushing the pixel data into the URL as URL parameters. Therefore, each unique favicon naturally has its own unique URL! You can bookmark your favicon to return and work on it later. Use the browser back and forward buttons to navigate the timeline of your edits.

Live Favicon Preview

With or without JavaScript, a preview of the most recent graphic state is displayed both as the favicon of the page and atop the Fill Selected Cells component. The JavaScript experience progressively enhances this by asynchronously updating the preview. As you work, you'll see colors changes and the preview live update! This is done by using inline SVG to create the at scale previews. These SVGs are wrapped in an <a download> so clicking them will download the current artwork as an SVG.

The favicon of the page itself is also asynchronously updated! So in browsers that support dynamic favicons, you'll see your edits reflected there as well.


Accessibility is water. We are all in need of and deserving of it. Working within the constraints of web standards, we construct our favicon editor from a semantic <table> of <input type="checkbox"> elements. With no additional expense, these elements inherit the implicit accessibility of native browser inputs. Each pixel or "cell" consists of an <input type="checkbox>" element and a corresponding label. These native elements provide keyboard focus, selection, and screen reader support along with compatibility with any assistive technology that integrate with native HTML <form> elements.

Our Fill Selected Cells component allows the user to input a color in several different ways. This presents convenience and accessibility to our users. Some users may prefer dictation and opt for the standard text input method. The text input accepts a hex color or standard CSS color name as input. In supported browsers, users will be presented with an option to use a colorpicker input. Finally, users can input or adjust colors using the RGB sliders.

For connivence a swatch component is lazy–loaded as needed. localStorage enabled users can save their frequently used colors for quick and easy access. Built upon a fieldset of radio inputs, the swatch panel leverages implicit accessibility.

The HTML5 datalist component is also used to enhance accessibility and usability. Once you set focus to the hex text input area a datalist component containing a list of CSS color names is lazy–loaded. Users in modern browsers that support the datalist component will get typeahead hints as they type in the hex input area. Browsers that do not support datalist will display a standard select component still allowing the user to chose between typing in a value or selecting one.

Accessibility Preferences

Users with localStorage enabled also are presented with Accessibility Preferences. At the /preferences page they will be presented with Legibility, Typeface, Contrast, Animation and Visibility components that allow them to manage related preferences. These preferences will be stored in localStorage and return visits will respond to user's preferences accordingly.


One of the thing I discovered through this process is that accessibility is a universal topic. It isn't just about screen readers and HTML. Accessibility applies to everything. I consider the import features not just power user features but accessibility features as well. They allow people to access their art quicker if they already have something prepared that they would like to import. This makes for a less fatiguing user experience.

Aside from the API, there are two ways to import art to the art–board.

  1. Upload a SVG or PNG image
  2. Use the PhotoShop ExtendScript

Both of these methods perform similar tasks. They take a graphic, size it down, crop it to 16x16, loop over each pixel, and direct you to the appropriate URL for your art.

With the Photoshop ExtendScript you can import the active Photoshop document. Open your art in Photoshop. Open makeanico.jsx in the Adobe ExtendScript toolkit and run the script.

Pro Tip: By slightly altering the script, entire directories can be imported which is how the World–Wide Favicons were imported.

Keyboard Friendly

Similar to accessibility, most of the keyboard considerations are provided by web standards and the browser. I didn't have to write any code for you to be able to easily jump around from cell to cell, input to input, or widget to widget. The semantics of the document provide that. Nevertheless, there are a few considerations and shortcuts I did make.

There are keyboard shortcuts for each of the selection tool buttons. Alt/Option + A will select all sells. Alt/Option + I will inverse the current selection. Alt/Option + D will deselect all cells. Mouse users can also click a cell and hold Alt/Option when clicking another cell to range select several cells. I do think additional keyboard considerations could be made but I'm waiting for more user testing and feedback before proceeding.

Progressive Enhancement

The base functionality provided by the raw HTML layer is enhanced by CSS styles and JavaScript features respectively. CSS provides a WYSIWYG editor and a nice layout. JavaScript is used to provide asynchronous enhancements. Without JavaScript, the form must post to the server to update the state and fill the selected cells with the chosen color. Progressive Enhancements remove this "hard reload" but updating the DOM asynchronously and pushing new state into the URL with the HTML5 History API. JavaScript is also used to make the Fill Selected Cells component more responsive. As the values of one input method are updated the others reflect the change. Typing "red" in the Hex input will update the colorpicker and RGB sliders, and vice versa.

Service Workers

A lightweight service worker is used to provide offline support and increase performance. Users not receiving scripts will still need to post to the server, but users receiving scripts should be able to go offline and keep making their favicon.

Bandwidth Considerations

Accessibility isn't just about color contrast and screen reader support. In an attempt to allow access to anyone, an accessible experience weeds out barriers to entry. Page weight is a barrier to entry, so we do what we can to keep the weight down and leverage the browser cache for the assets we do load.

One page experiences like MakeanIco can increase their PageSpeed score by inlining critical CSS and small scripts. However, we don't inline our enhancements. To do so would not leverage the browser cache effectively. Loading enhancements through HTTP requests allows them to be cached across all the URL endpoints of the application (of which there are many – each icon has its own URL).

ARIA Considerations

ARIA is salt. So we use it sparingly and only as needed. I'd rather add seasoning later after feedback and upon request. So much of the foundation is semantic HTML that little ARIA is used or needed. Accessibly hidden text is used to allow the HTML to be semantic and audible to assistive technology while still visually appealing and contextual to sighted users.


Any icon's art is contained in the URL. Each cell, or pixel, is represented as a URL parameter. The web app uses the trendy new 8 Digit Hexadecimals to store color so to draw an icon where the first pixel is 50% red you'd use /?c1=0xff000080. Each cell has a numeric index starting from c1 and ending in c255. The value should be a 6 or 8 digit hexadecimal starting in 0x not #.

URL parameters can be used to set the initial color input type as well as the initial fill color. For example, ?fill=0x00ffff0c&colorby=rgba. These defaults will not override an values found in the users localStorage.

There are /make/favicon.svg, /make/favicon.png, and /make/favicon.ico endpoints that can be used to load a dynamic favicon graphics in the requested format. For example /make/favicon.svg?c23=0x0cFF00FFFF or /make/favicon.png?c23=0x0cFF00FFFF&dl=1 for an immediate download.

World–Wide Favicon Flags

In the spirit of the World–Wide Web, MakeanIco provides a public API for favicons of each country flag.


Flag Country Code Edit Page SVG Icon PNG Icon ICO Icon
nl /icos/flags/nl /get/svg/icos/flags/nl /get/png/icos/flags/nl /get/ico/icos/flags/nl
us /icos/flags/us /get/svg/icos/flags/us /get/png/icos/flags/us /get/ico/icos/flags/us

Find national flags for every country in the Wiki.

Note: To trigger an icon to be downloaded add a &dl=1 URL parameter to the get/svg, get/png, or get/ico request.

Brands and Icons

I've added endpoints for some of my favorite favicons. Follow the links in the icon column to navigate to the edit screen of your favorite icons.

Icon Edit Page SVG Icon PNG Icon ICO Icon
/icos/modmore /get/svg/icos/modmore /get/png/modmore /get/ico/modmore
/icos/thinkful /get/svg/icos/thinkful /get/png/thinkful /get/ico/thinkful
/icos/sterc /get/svg/icos/sterc /get/png/sterc /get/ico/sterc
/icos/markuptips /get/svg/icos/markuptips /get/png/markuptips /get/ico/markuptips
/icos/modx /get/svg/icos/modx /get/png/modx /get/ico/modx
/icos/apple /get/svg/icos/apple /get/png/apple /get/ico/apple
/icos/safari /get/svg/icos/safari /get/png/safari /get/ico/safari
/icos/10kapart /get/svg/icos/10kapart /get/png/10kapart /get/ico/10kapart
/icos/microsoft /get/svg/icos/microsoft /get/png/microsoft /get/ico/microsoft
/icos/windows /get/svg/icos/windows /get/png/windows /get/ico/windows
/icos/edge /get/svg/icos/edge /get/png/edge /get/ico/edge
/icos/google /get/svg/icos/google /get/png/google /get/ico/google
/icos/chrome /get/svg/icos/chrome /get/png/chrome /get/ico/chrome
/icos/opera /get/svg/icos/opera /get/png/opera /get/ico/opera
/icos/vivaldi /get/svg/icos/vivaldi /get/png/vivaldi /get/ico/vivaldi
/icos/facebook /get/svg/icos/facebook /get/png/facebook /get/ico/facebook
/icos/twitter /get/svg/icos/twitter /get/png/twitter /get/ico/twitter
/icos/smashingmag /get/svg/icos/smashingmag /get/png/smashingmag /get/ico/smashingmag
/icos/gmail /get/svg/icos/gmail /get/png/gmail /get/ico/gmail
/icos/firefox /get/svg/icos/firefox /get/png/firefox /get/ico/firefox
/icos/mozilla /get/svg/icos/mozilla /get/png/mozilla /get/ico/mozilla
/icos/zeldman /get/svg/icos/zeldman /get/png/zeldman /get/ico/zeldman
/icos/w3c /get/svg/icos/w3c /get/png/w3c /get/ico/w3c

Accessibility Proclaimer

This web app strives for WCAG 2.0 Guidelines Level AA. Please open an issue for any accessibility issue, feedback, or concern.

Browser Support

Makeanico has excellent browser support but isn't without issue.


Makeanico is architected starting with an HTML–first web standards layer and is progressively enhanced. It therefore has very ubiquitous support. Even IE6 users can use the semantic form and synchronously illustrate an icon. Lynx users can read the contents of an icons artboard.

That said, there is a known issue in IE and Edge will URLs exceeding 2083 characters. This causes issues with exporting dirty art–boards in Edge. Edge is tracking an issue to resolve this so no action is being taken. Export of very dirty art–boards will land when this is addressed by Edge. That will be an exciting update!

Desktop Async

Vendor Version
Chrome 53.0
*Edge 38.0
Firefox 49.0
*IE 11
Opera 39.0
Safari 10.0
Vivaldi 1.4.5

*Does not export very dirty art–boards

Mobile Async

Vendor Version
iOS 10.0
Android latest

If you are experiencing an issue please let us know.


The synchronous or no-js experience should work in just about any browser so we'll take bug reports for any browser.


Scripts are only loaded if needed and pass feature tests. Polyfills are only loaded if needed and fail feature tests. Polyfills for fetch() and Promise are lazily loaded if needed. These are both needed for IE11. The fetch polyfill is needed for Safari 10.0 and iOS 10 but Safari will soon shed this polyfill.