Reading List
The most recent articles from a list of feeds I subscribe to.
Browser API Special and CSS Day
This week, the 5th edition of the yearly CSS Day took place. It was preceded this year by something called Browser API Special: a full day about JavaScript APIs.
I’ve done a quick writeup below, with some of my takeaways and some things I thought were quite brilliant. If you’re interested in more of CSS Day, the organisers have announced there will be another edition next year, updates for which are available through the newsletter. If you want to experience more of this year’s edition, there will be videos!
Access to almost everything through JavaScript
The general theme of Browser API Special was that browsers are starting to expose so much stuff via JavaScript. Animation, virtual reality, payments… more and more is made available through useful (and some less useful, see below) DOM APIs.
The day started with Rachel Nabors, who explained how CSS transitions, CSS animations and SVG animation standard SMIL are now combined into one Web Animations API. This API provides a bunch of methods to let us animate stuff on the web more efficiently. Her examples were helpfully based around Alice in Wonderland. This made the code examples much easier to understand (cake.animate()
instead of foo.animate()
).
Then Peter-Paul Koch counselled us (PDF) on the usefulness of :valid
/:invalid
in CSS and the Constraints Validation API. The latter is a standard that, in theory, lets us offload the logic of dealing with form validation and requiredness onto the browser. It and the CSS pseudo classes contain some helpful stuff, but there are issues with timing (errors on keypress
or submit
, where blur
would make more sense), customisability (can’t change what the error message is) and stylability (can’t style error messages).
Philip Walton then went into how to polyfill stuff in CSS, or in other words: add custom functionality to it. Wait, wut, extending CSS via JavaScript? Yes! Suppose you would like to have a random()
function in CSS that you could use within calc()
, what would you need to do to make it work? Browsers let us access the CSS Object Model, but using that to then try and replicate cascading and media queries and what not after the browser has already evaluated all its stuff raises the question: why doesn’t the browser help us a bit here? Houdini has a set of low-level JavaScript APIs that let us access the stuff browsers do when parsing CSS (paint, layout, parsers, etc), which is awesome as it lets us interact with our custom functionality right in the browser dev tools.
Ruth John showed us that websites that are enhanced with audio aren’t always an awful thing: wonderful experiences can be made with it as long as we’re considerate and subtle. She also showed what the code looks like to interact with fragments of audio. From creating a new AudioContext
to using that to do all sorts of wonderful things. You can use Web Audio API to create your own sounds or do so with the help of MIDI devices. Very inspiring!
Ada Rose Edwards asked us all to put on the VR headsets that had been handed out during the breaks, and showed us some cool demos of WebVR. There was even one that had ourselves in it, which was an interesting experience. At some point it had 178 of us logged in at the same time.
Mike Taylor shared all sorts of weird things that are now in browsers and probably will be in browsers for the foreseeable time, for all kinds of weird and usually historic reasons. He explained it is good if functions don’t have all sorts of side effects, and that window.showModalDialog
does. How stuff gets capitalised is complex and various mistakes have slipped into browsers regarding this. Vendor prefixes can be hard to get out of browsers once they are no longer needed.
Patrick Kettner went into Progressive Web Apps and kind of showed how turning your app into one can benefit your app’s potential: it can show up in the Windows app store (as Bing indexes PWAs and can automatically add them into the store if you want that), and lets you access platform APIs for things like Bluetooth and media keys from the browser.
Chris Wilson, who was the first to implement CSS in a browser (way back), talked about both managing credentials of users (and ways to make that easier for users) and the Web Payments API. This lets the browser be aware of a user’s payment methods and allow users to pay from their browser. Lots of food for thought, i.e. what sort of new payment providers will come up, and will it be useful if the Dutch iDeal system hooks into this (I think it already works very well on mobile, but this may vary per bank)?
There was so much stuff in today’s talks that, had you asked me five years ago, I would have thought could only be done in native apps. Native apps have traditionally been able to do more with hardware than web apps, but that seems to have rapidly changed. This is a good time to be a web developer.
The history, current state and future of CSS
Day two had everything in it from the history to the future of CSS. Generally speaking, I was very pleased to see into how much detail many of the speakers went. As someone who writes CSS almost daily, I benefit a lot from its simplicity. A lot of things happen automatically and ‘just’ work. To make that possible, specifications and browsers do tend to get quite complex and detailed. CSS Day 2017 was a great chance to learn more about those details, and will likely help me get even more out of CSS.
CSS-inventors Bert Bos and Håkon Wium Lie opened the day. I had seen both talk before, but never in the same room. They used their session to reflect on stylesheets: what would they have done differently, what are they happy with? The syntax, absolute positioning, collapsing margins and scrollbars were some things the two would have liked to have a second chance at.
Bert and Håkon looking at the first proposal for “Cascading HTML stylesheets”. “HTML” was dropped from the name, as there were other markup languages around and stylesheets seemed fit for those, too.
An interesting aspect in the discussion: CSS was meant for documents, which is what Bert and Håkon had a history in, as opposed to UIs. There has been talk about a second language to do UI styles. An example of a difference: documents benefit from collapsing margins and text flowing the way it does, but that kind of stuff can get frustrate styling UIs.
Another fascinating topic was that they would have loved CSS to consider wholes rather than parts. In order for something like ‘make all p’s display in italics’ to work, an alghoritms can do this thing by thing. In order for something like grid to work, an alghorithm needs to consider the whole (sub)tree and place stuff.
A general theme I took from Bert and Håkon’s discussion was that so much effort was put into keeping CSS as simple as possible. Stuff like the cascade: it was put in so that authors can write a lot less CSS. Food for thought!
After this magnificent opening, Rachel Andrew talked to us about CSS Grids. I loved how the talk really went into the nitty gritty, and I have lots to look up at home: blockification and inlinification for example, and the difference between outer and inner display types. Rachel also went into why subgrid is not happening (yet) and what makes it hard to replicate with other CSS properties. She also explained how CSS Grid’s writing mode awareness impacts its syntax. Lastly, a great tip she gave: if you’re confused about what your Grid is doing: try and use longhand syntax to make debugging easier.
After the break, Mark Boulton gave us an introduction in grid design for the web. After a bit of graphic design history, and making the argument against just using 12-column grids for everything (hello Bootstrap!), he explained a grid design method: start with a constraint, base units on that constraint and then create a grid based on those units. All of this, he emphasised, fits the best order to grid design: start with content and base a grid around that, not the other way around.
Jen Simmons talked about Writing Modes in CSS: a standard that lets us build websites with content in all modern (spoken) languages, whether they are written horizontally or vertically, left to right or right to left. There are major differences here in languages: Arabic (rtl), Mongolian (vertical, ltr) and Han-based languages (vertical, rtl or horizontal, ltr). Inline direction and block direction are important aspects in understanding Writing Modes (check the spec for details). If you’ve switched writing modes (with lang
and dir
attributes), the browser will then do its stuff (create space for elements; apply cursors, etc) with the correct writing mode in mind.
Jen Simmons with her slide that shows examples of ‘logical properties’ in CSS: rather than ‘left’ and ‘right’, ‘start’ and ‘end’ are more meaningful as they are direction-agnostic.
Variables may be a thing most CSS developers know from preprocessors, but they have become a (well-supported) thing in CSS. With --foo: bar
we can set custom properties, and Gregor Adams showed some amazing examples of what can be done with that. A supports table where properties were updated within @supports
queries so that they only got the true
value when the feature is supported (this works because they are scoped), as well as an actual working calculator. Awesome stuff!
The author of many modern CSS specs, Tab Atkins, was in today to tell us all about Houdini, which is a set of JavaScript APIs (and some CSS stuff) that makes CSS extendible by its users. If the community goes ‘we’d like masonry in CSS’ or ‘can we have a random() function?’, it will soon be able to add those things to CSS themselves, using JavaScript. His talk contained near future stuff like methods to register custom properties, as well as new things that are yet to be finalised, like custom units and custom paint functionality. And then there was stuff that really does not (yet) exist: a CSS.parseRule()
method so that we don’t have to reimplement all stuff browsers do with CSS in our own JS libraries, custom functions and custom @-
rules.
Tab Atkins introducing what could be quite a simple way to define custom functions in CSS (does not exist yet)
The last block of talks started with CSS-Tricks and CodePen founder Chris Coyier. In a talk full of questionable pop music, but also many tasteful examples, he showed us best practices for making a fan site for our favourite band. Four properties were central to his talk: shape-outside
, offset-path
, clip-path
and d
. He demoed how they can help making a lyrics page have more interesting visual effects. All four are awesome properties well worth checking out, but d
stood out to me: its value is a path()
function that lets you override a path
in an svg. I had no idea this sort of stuff could be done!
Closing this wonderful day was Stephen Hay. He talked about various things that people with bad intentions can do to exploit security issues caused by bad (but also good) CSS. Who would have thought that using classnames could lead to vulnerabilities? Some stuff is luckily no longer possible, like adding JavaScript into the url()
method of CSS backgrounds and looking into a user’s browsing history with getComputedStyle
on visited links. Stephen also discussed dark patterns, like unsubscribe flows that lure you into resubscribing.
I’ve had a blast! After having worked as conference staff for the first 4 editions, this year I had the honour to work on the design part of CSS Day, doing poster, slide and badge designs with silly CSS jokes on. I was happy (almost) all of it worked out well. And that I got to watch all the talks!
The talks were just fantastic. There are a lot of specs and APIs that I will have to read up on, but I’ve also learned plenty of practical stuff to use at work straight after this weekend. It was awesome to see how much effort spec writers and browser makers put into making stuff easier and simpler for both end users and web developers. They also do more complex stuff: Houdini comes to mind. Although that might not be fair: Houdini will help people extend CSS, and although the extending itself may be complex for people who normally write CSS, making use of extensions can be made very simple. Just like existing CSS properties and functionalities.
For those who have missed this year’s edition, as I said before, there will be a UX Special and CSS Day next year: 14-15 June 2018 is the date to save!
Originally posted as Browser API Special and CSS Day on Hidde's blog.
ICONS: five questions about screen readers
This week I attended the last ICONS meetup of this academic year, which had as its speaker Léonie Watson. She shared five questions related to screen readers.
ICONS is an event organised by the design department of the Hogeschool van Amsterdam. They invite (web) design related speakers, as well as students and people working in the web industry, so that they can meet each other and learn stuff. Awesome! I took some notes of Léonie’s talk, please find them below.
Léonie Watson in Amsterdam
Five questions
Who
Some might think screen readers are just used by blind people. Léonie explained they have a broader audience: people with impairments in the autistic spectrum use them (screen readers provide a way to navigate information with less distraction) and people with some forms of dyslexia (as listening may be easier for them than reading). In the future, many others may use audio to navigate information: think about digital assistents like Siri and Cortana, or voice interfaces in the car.
What
A screen reader, Léonie explained, ‘translates on-screen content into synthetic speech’. Some can also do braille, but equipment for this can be costly. They are available on most platforms, including mobile platforms. Some are free (NVDA, VoiceOver), others can be quite expensive (JAWS). They don’t read the whole page as if it were plain text (as they used to do in the old days), they interpret a page structure and let their users benefit from shortcuts. For instance, a screen reader user could navigate through just headings, or press the ‘read next paragraph’ shortcut.
Where
Most of the interpretation for screen readers happens on the OS level. Things in the OS expose their role, name and state, and through platform level accessibility APIs, this information is referred to the screen reader. There exist accessibility mappings between the OS and the browser. Note that browsers don’t always support all accessibility features: they sometimes support a new web platform feature, without also accessibility supporting it. Which means:
(…) it is usable by people who rely on assistive technology, without developers having to supplement with ARIA or other additional workarounds.
(source: HTML5 Accessibility, a website that holds information on accessibility support in browsers)
When
In the browser, an accessibility tree is generated based on information in the DOM tree. This process basically means that the browser is figuring out what the role, name and state of things are, and puts them in a tree. Screenreaders use platform APIs to access information in this tree, and then make all the shortcuts possible for their users.
A quick note on this: as a front-end developer, you can directly influence what information gets exposed by making sure you use a semantic HTML structure and get your labelling right (see also: Accessibly labelling interactive elements). States are harder, but there are native HTML attributes available for this (i.e. hidden
and open
, as well as ARIA polyfills).
Why
Then, the last question: why? This was mostly aimed at designers and developers making websites: why would they support screen reader users? Léonie gave lots of reasons: it feels good, it’s fun to work on something others enjoy using (isn’t that why we add subtle animations, choose good looking and nicely readable fonts, make our stuff load fast etc). Secondly, it is a professional responsibility to ensure what you make is usable. Thirdly: as a designer or developer, you have a choice (as opposed to the users that rely on screen readers to access your product).
These five questions are certainly something to think about when you are making websites that are used by people who rely on screen readers. And that’s very likely to be every website or web application.
It was a pleasure, as always, to hear Léonie talk about using screen readers and share her knowledge. Thanks to the HvA for setting up this event!
Originally posted as ICONS: five questions about screen readers on Hidde's blog.
I, Daniel Blake
In what was a truly inspiring afternoon at the client last week, we watched the English film I, Daniel Blake. It is about a carpenter of nearly the pension age, who applies for income support following a heart attack.
The client is a semi-government organisation that builds software for citizens to manage income support applications online. When I first watched I, Daniel Blake when it came out in November last year, I was moved, but also inspired. This film was about the very thing we were working on, and there was so much to be learned from it.
The plot of the film, which won the Palme d’Or at Cannes in 2016, is fairly simple (spoilers ahead). Mr Blake wants to apply for income support, as his doctor has told him he should not work. For some reason, his application gets denied. A mountain of bureaucracy follows soon after. The staff tell him he could appeal, but as an appeal could take months, they recommend him to apply for job seekers’ allowance instead. He does so, but is then asked to prepare a CV and apply for jobs, which he ends up getting offered but cannot take because of the aforementioned doctor’s advice. Mr Blake patiently jumps through all the hoops the system forces upon him, until the point he does not want to take it anymore.
Stills from the film
People as numbers
In the film, Mr. Blake often tells staff he engages with about his heart condition. He’s the example of a perfectly reasonable person. Yet he finds they ignore what he says, and are only willing to look at what the system says about him (which is: ‘application denied’).
The film shows a bureaucratic system that regards people as applicants, service users or ‘clients’: like numbers. I don’t think that is inherently good or bad. All software that deals with people represents them as fields in a database. I guess this aids efficiency: if all people are in a database, it is easier to make estimates, identify people and their details, et cetera. This only turns into a problem when people are also reduced to numbers. If the number are the only thing the system is interested in.
What happens to Daniel Blake is an example of the latter. Was he treated humanly, a heart attack and a doctor’s recommendation would be plenty of indication for the system to just give him the income support he applied for. Now that he is treated only as a number, the system forces him to jump through hoops needlessly.
Human judgment
Services that used to be mostly physical are transforming to their mostly digital equivalents. Although I am just a front-end developer, I am often involved in teams that realise such transformations. Most of my work revolves around trying to make interfaces easier to use and being inclusive to all who need to use them. I, Daniel Blake was a good reminder that those ambitions can still leave people frustrated.
However user-friendly we try to make our interfaces, a digital service is not going to help everybody. Sometimes the best interface is an actual human being that helps people. Machine judgment is great and efficient, but the system needs human judgment. Equipped with appropriate authorisations, humans trump digital services: the very person sending Mr. Blake to a cv workshop could have just authorised his income support (and gotten on with the next applicant – now that’s efficiency). To help him skip the hoop-jumping.
It was fantastic to get the chance to watch this film with colleagues, and I can’t recommend it enough to peers who work on digital services (government or otherwise). I’ve heard some say it is overly dramatic. It may be, but I found it realistic and close to reality, too. Looking forward to hearing what others think about it!
Originally posted as I, Daniel Blake on Hidde's blog.
Accessibly labelling interactive elements
All interactive elements should have accessible names, says the W3C about using ARIA. They can also have descriptions. Names and descriptions can help your users figure out which elements they are interacting with, and what they are for. In this post I will explain implicit and explicit labelling methods and discuss how to future-proof your labels.
Accessibility tree
Besides a DOM tree, browsers build an accessibility tree when they render pages. It contains all interactive elements, so that it is easier for assistive technology (AT) to expose a page and its controls to their users. Being aware of how your markup influences the accessibility tree helps a great deal in making your app work for those users.
Interactive elements are things like links, buttons, form inputs and <details>
with a <summary>
and <audio>
with a controls
attribute.
An accessibility tree contains accessible objects, which can have these name-related properties:
- accessible name
- accessible description
The properties that result in an accessible name are often called label, this is why from the next section I will be calling them labels.
Name vs description
Both the accessible name and the description can provide text alternatives for interactive elements. The difference between the two is that the description is more detailed than the name, and that it is additional, where a name is essential. Generally, it is more useful to provide a name and no description rather than the other way around.
Inspecting the tree
If you want to look at the accessible objects in pages you are building, install the Accessibility Dev Tools in Chrome. With those turned on, go to a website of choice, inspect an element and look at the properties under the Accessibility tab. If you’re on Windows, aViewer lets you browse what accessibility APIs of multiple browsers are exposing.
Labelling with standard HTML
Most of the time, browsers can compute these things from your HTML structure. To make this work, just use standard HTML conventions. With those, your accessible names and descriptions are implicitly exposed.
Labels
This structure, for example, uses a <label>
:
<label for="element-1">Phone number</label>
<input type="tel" id="element-1" />
The browser infers the accessible name for the input from the label, so no extra work is required, the input will appear in the accessibility tree with a sensible name.
You can also label an interactive element without rendering the label on screen, with a title
attribute:
<audio src="great-song.mp3" title="Great song" controls></audio>
‘Great song’ will be used as this audio fragment’s accessible name, so a user trying to figure out what this audio file is, will be presented with a label.
Descriptions
There are various native ways to associate a description with an element, for example:
- a
<legend>
in a<fieldset>
- a
<caption>
in a<table>
None of the above are interactive elements, though. This is because for interactive elements, there is no implicit method to add a description without ARIA.
Labelling with ARIA
For some elements there are no labelling methods that use standard HTML. The <label>
element, to name one, is just there for form controls. WAI-ARIA allows for more explicit association of labels, with these two properties: aria-labelledby
and aria-describedby
.
Labels
With the aria-labelledby
attribute, you can use another element as the label for your interactive element. This is for cases where no native labelling method is available.
The value of aria-labelledby
should be the ID of the element that labels it. You can even add multiple IDs (separated with spaces). Use with care, as in many cases this would cause the label to be unhelpfully long.
Example:
<h2 id="label-lasagna">Lasagna recipe</h2>
<p>To make this lasagna, just follow these
10 simple steps! <a href="/posts/name-change"
aria-labelledby="label-lasagna">Read more</a></p>
“Read more” is not a useful label, as you can imagine what would happen if this component is repeated a couple of times on a given page. A screenreader user tabbing through the links on the page would just hear:
Link Read more Link Read more Link Read more
As the title above the text, I have chosen something that would work well as a label for the link text. I’ve associated it with aria-labelledby
. In many screenreaders, a user that tabs over the link will hear ‘Link Lasagna recipe’ instead of ‘Read more’ (PowerMapper have comprehensively tested this technique).
Another example:
<h3 id="label-song-4">4: It never entered my mind</h3>
<p>The fourth song, “It Never Entered My Mind” is a show tune from the 1940 Rodgers and Hart musical Higher and Higher (1940), where it was introduced by Shirley Ross. The Miles Davis recording was used in the movies Runaway Bride (1999) and the Lenny Bruce biopic Lenny (1974).</p>
<audio src="4-mind.mp3" controls aria-labelledby="label-song-4">
This exposes ‘4: It never entered my mind’ as the accessible name of our audio fragment. When a user tabs onto the audio button, a screenreader should read out the title (this is in theory; in practice, support for aria-labelledby
on <audio>
is actually quite bad).
If, for some reason, your label is not visible on screen and you want to add it for screenreaders only, you can also use aria-label
with the actual label text as its value. I would not recommend this in most cases — if a label is helpful, why only show it to some of your users?
Descriptions
The aria-describedby
lets you associate an element with another element as its description.
Example:
<input type="checkbox" id="accept-terms" aria-describedby="small-print" />
<label for="accept-terms">Accept our terms</label>
<p id="small-print">Note that upon accepting our terms, you also agree to us calling you with offers for beautiful kitchens, mortgage plans and magazine subscriptions.</p>
The small print text will display, but by associating it with aria-describedby
you make it easy for assistive technologies to provide a description for the input to their users.
This can help screenreader users that are tabbing through the form, to make a better choice about whether to accept the terms.
Future-proofing labels and descriptions
Arguably, the best labels and descriptions are the ones that are up to date. The ones that have been added properly and provide useful, sensible text, explaining what a control is for.
This is harder than it may sound. As Mark Pilgrim concludes in his post about longdesc, a theoretical solution does not necessarily provide ease of use to your users. He cites statistics that say that “less than 1% of images that provide a longdesc attribute are actually useful”. Wow!
Risk of breakage
One way less useful labels can make their way into your product, is because of human error or even unintended sloppiness. By yourself, by another team member, by a team member 3 years from now. A developer could add the proper associations now, only for someone later in the process to break it unintentionally.
Maybe one of the content editors forgot that a heading was associated as a button’s label, and changes it into something that no longer labels it. Or it could be that a developer isn’t aware of the latest aria-*
attributes and breaks something. Note that such breakages can happen without anyone noticing.
Avoiding breakage
An approach to get most out of your labels in the long run: make sure the labels and descriptions can be seen by everyone on your product team and by all of your users. It then happens where everyone can see it, which helps fixing any breakage as it happens.
Another thing that can help is to have a component based approach. In a component, you would usually associate variables rather than actual values (i.e. aria-labelledby=""
instead of aria-labelledby="might-forget-this-is-here"
.)
Lastly, I would recommend making sure the labelling approach is addressed in documentation and that all testers / reviewers in your team(s) are aware of what the attributes do, so that they can spot any issues before they go live.
In conclusion
To help all users make sense of your page, ensure all interactive elements are properly labeled and have descriptions where appropriate. There are several native techniques to do this, as well as ARIA attributes.
To associate labels with interactive elements, always prefer native methods as they will have the best browser support and there is most chance your users and other developers are familiar with them. If native labelling really is not possible, use aria-labelledby
(if label is rendered on screen) or aria-label
(if label is not rendered on screen). To associate descriptions with interactive elements, use aria-describedby
.
Further reading
- The Browser Accessibility Tree
, article by The Paciello Group with examples of accessibility trees in Firefox, IE and Chrome for
video
elements - Web Accessibility with the Accessibility API by Léonie Watson and Chaals McCathie Nevile on Smashing Magazine
- Labels and identification at Web Usability
- The spec on how labels and descriptions are computed
Originally posted as Accessibly labelling interactive elements on Hidde's blog.
How to customise a file upload control
If your website lets users upload files, you have probably compared the default file upload control with custom solutions. In this article I will show you can have both, using a method that uses the default input, but looks completely custom.
The default file upload
This is a default file upload:
<input type="file" id="file-upload" />
<label for="file-upload">Upload a file</label>
It is one of those HTML elements that has a lot of usability and accessibility built in.
To name a few things that you get for free when using the native <input type=file>
:
- it can show the file name of the upload file
- it can be used with just a keyboard
- on phones, it will let your user take a photo or choose to select files from specific apps
- on desktop browsers, it will let your user browse the file system
- it can be set to allow
multiple
files to be selected - it can distinguish between file types with its optional
accept
attribute - it exposes a useful
files
DOM attribute for usage in your scripts (as well as a slightly less usefulvalue
) - it integrates with your browser’s built in error mechanism
File input that matches your brand
Something you probably don’t get for free is a smooth integration with your brand’s design guidelines. They might prescribe buttons to be blue or rounded corners everywhere.
The HTML example above related the input with a corresponding label. I used corresponding for
and id
attributes, but wrapping the input
inside a label
works just as well.
In all browsers, you will find that you don’t actually have to click the input to activate it: clicking the label will do. Because of this, the trick, which I learned from Krijn, is simple: hide the <input/>
and style the <label>
.
Hide the input
You want to hide the input, but do that in a way that screenreaders don’t assume it does not exist. The method for hiding is to make it ‘visually’ hidden, but not actually hidden to assistive technologies.
This is one way:
input[type="file"] {
opacity: 0; /* make transparent */
z-index: -1; /* move under anything else */
position: absolute; /* don't let it take up space */
}
There are various different approaches to this, the important thing is that the method does not render the input unusable (as visibility: hidden
would, for example), or move it off screen (as left: -9999em
would), as that would move any native error messages off screen as well.
Style the label
Now, only the label is visible and you can style it however you like. You can make it look like a button, add background gradients, drop shadows, whatever your heart desires.
Add focus styles
It would be good to add the focus styles that your input had to the label. You can select this in CSS as follows:
input[type="file"]:focus + label {
outline: 2px solid; /* example focus style */
}
TL;DR
To make a custom file upload control, use a standard file upload and label, then visually hide the input and style the label.
Update: On Twitter, Phil pointed out that if you use the above example, users can’t see the file name of what they’ve uploaded. This is a great point; in my own implementation I’ve used some JavaScript to access the input’s files
property onchange
of the input, which I then inserted into the page if it had changed (I’ve created a Codepen to show how to do this)
Originally posted as How to customise a file upload control on Hidde's blog.