CSS Modal

Using CSS3 tech­niques a modal box can be cre­ated with­out JavaScript or images. With a bit of ani­ma­tion, tran­si­tion and trans­form, it can be made that lit­tle bit more special.

CSS Modal Experiment

Modal exper­i­ment updated for Fire­fox 10 which has bet­ter trans­form, tran­si­tion and ani­ma­tion per­for­mance. Also sup­ports 3D transforms.

In this exper­i­ment, click­ing an ‘open’ link pops up a dia­logue with a smooth hard­ware accel­er­ated bounce (where sup­ported). When open all other ele­ments on the page are non-clickable. Clos­ing the modal is also ani­mated, with a min­imise effect. I’ve marked up the modal using <aside>, but depend­ing on the pur­pose of yours, <nav> or prob­a­bly <details> might be more appropriate.

Of course, using images and JS will only make the modal bet­ter, and some­thing like hit­ting ESC to close will never be repro­duced in CSS. Pure CSS is rarely the best production-ready solution.

How to

The :tar­get pseudo-selector changes the style of a tar­geted ele­ment. Com­bin­ing a link point­ing to an ele­ment with :tar­get and alter­ing visibility/display/opacity gives a hide/show mech­a­nism. To facil­i­tate the ani­ma­tions, which were jerky when using display:none, I’ve used a com­bi­na­tion of :tar­get, opac­ity and pointer events:

.modal {
opacity: 0;
pointer-events: none;
}

.modal:target {
opacity: 1;
pointer-events: auto;
}

The modal is two parts, one part con­tainer, one part con­tent. Ide­ally the con­tainer would be gen­er­ated using a pseudo-element, but I haven’t got that work­ing yet. The con­tainer spreads across the whole page and dims the back­ground with rgba. A high z-index puts the modal on top.

.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 10000;
…
}

The con­tent is posi­tioned roughly in the mid­dle and is pret­ti­fied with a sprin­kling of text shadow, bor­der radius, box shadow and gradient.

There are two ani­ma­tions, one named “bounce” (scale to slightly larger than nor­mal, then fall back) and another, “min­imise”, which act on the con­tent part. These com­bine with a sep­a­rate opac­ity tran­si­tion on the container.

The sim­ple opac­ity transition:

.modal {
…
-webkit-transition: opacity 500ms ease-in;
-moz-transition: opacity 500ms ease-in;
transition: opacity 500ms ease-in;
}

The scal­ing ani­ma­tions, although only 2D, uses scale3d for hard­ware accel­er­a­tion. To make the bounce more real­is­tic box shadow is also ani­mated, which comes with a per­for­mance hit. Show­ing only the webkit ver­sion for brevity:

@-webkit-keyframes bounce {
  0% {
  	-webkit-transform: scale3d(0.1,0.1,1);
  	box-shadow: 0 3px 20px rgba(0,0,0,0.9);
  }
  55% {
  	-webkit-transform: scale3d(1.08,1.08,1);
  	box-shadow: 0 10px 20px rgba(0,0,0,0);
  }
  75% {
  	-webkit-transform: scale3d(0.95,0.95,1);
  	-box-shadow: 0 0 20px rgba(0,0,0,0.9);
  }
  100% {
  	-webkit-transform: scale3d(1,1,1);
  	box-shadow: 0 3px 20px rgba(0,0,0,0.9);
  }
}

@-webkit-keyframes minimise {
  0% {
  	-webkit-transform: scale3d(1,1,1);
  }
  100% {
  	-webkit-transform: scale3d(0.1,0.1,1);
  }
}

To change the ani­ma­tion on open we can use the cas­cade and over­ride the default ani­ma­tion with a more spe­cific one, using :tar­get again:

.modal > div {
…
-webkit-animation: minimise 500ms linear;
}

.modal:target > div {
-webkit-animation-name: bounce;
}

The close but­ton is a hid­den close link with a styled ::after pseudo-element that scales on hover/focus/active. As we’re hid­ing the orig­i­nal close link, there are some hoops to jump through to make the :focus state change on the pseudo-element. The tra­di­tional clip, text indent or vis­i­bil­ity hid­den meth­ods all fail, and I’ve resorted to color:transparent and some spe­cific focus styles to over­ride the con­fused native ones.

.modal a[href="#close"] {
…
color: transparent;
}

.modal a[href="#close"]:after {
content: 'X';
display: block;
…
}

.modal a[href="#close"]:focus:after,
.modal a[href="#close"]:hover:after {
-webkit-transform: scale(1.1,1.1);
-moz-transform: scale(1.1,1.1);
}

.modal a[href="#close"]:focus:after {
outline: 1px solid #000;
}

Caveats

It won’t work in IE8 and below, there’s no pointer-event sup­port and opac­ity is poorly imple­mented. IE9 sup­ports :tar­get but no pointer-events. Some IE spe­cific styles could eas­ily switch the opac­ity tog­gle to a dis­play or vis­i­bil­ity one.

Users will still be able to tab through the links in the back­ground and acti­vate them. This only becomes an issue if you ignore the focus state…

As I alluded to at the start, some JS hooks for key­board inter­ac­tion wouldn’t go amiss, ESC to close and some­thing to pull focus to the cur­rent modal and then back again on close.

The close but­ton would prob­a­bly look bet­ter with an image and the markup uses an extra con­tain­ing ele­ment, which is always undesirable.

Ani­ma­tions can be great, in small quan­ti­ties, but some­thing that ani­mates too much and gets in the way of func­tion­al­ity is a major draw­back for users. It would be quite easy to go over­board with this.

I’ve added a fol­low up post that addresses some of the caveats and makes this a lit­tle more cross browser com­pat­i­ble: CSS Modal Fol­low Up.

Paul Hayes Paul Hayes is a developer at Last.fm. You should follow him on Twitter, where he talks about UX, HTML, CSS and JavaScript, amongst other cool stuff.

26 Comments Post your own

  1. Kieran Bowler

    Thanks for post­ing this up with such a detailed descrip­tion. I’ve only this week come across :tar­get. Lots of poten­tial uses but the usage of it seems a lit­tle awkward.

    Pointer events are some­thing I’ve never heard of before this. I’ll def­i­nitely be look­ing into them

  2. Chris Coyier

    Love this, great work.

    For any­body that looks at this and scoffs at the lim­ited browser sup­port, con­sider some slight alter­ations. Use JavaScript for the trig­ger­ing it open and closed (instead of :tar­get and pointer-events). Then you are still lever­ag­ing CSS for the design and fun­zies, but get way deeper browser support.

  3. Divya Manian

    A note that pointer-events prop­erty is not sup­ported yet in HTML for Opera, how­ever, it does work in SVG.

  4. Milos

    Really impres­sive, Paul. Prob­a­bly going to recre­ate this over the weekend.

    Keep up the good work.

  5. Nicolas Gallagher

    This is a cool exper­i­ment. One other caveat I didn’t see men­tioned is how click­ing the back but­ton after clos­ing a modal re-triggers the appear­ance of the modals.

  6. Justin

    So sad, so many nifty CSS only solu­tions tuto­ri­als around the web these days. Until there is bet­ter IE sup­port, I just don’t have time to check them all out as much as I’d like to.

    Thanks for the post. Sim­ple solu­tion. wish it could be standard.

  7. David Leggett

    This is won­der­ful, very well done!

    Like Nico­las Gal­lagher noted, I’d say the major draw­back right now is the back but­ton reopen­ing the modal.

  8. Andy Dust

    Very neat, I exper­i­mented with the same tech­nique awhile ago for a pure css lightwin­dow type photo gallery.

    But as oth­ers have men­tioned, the way :target/fragments works with browser his­tory (and back/forward but­tons) makes this solu­tion untenable.

    Andy.

  9. mohdhazwan

    awe­some man! seri­ously awesome.

  10. Jake

    Awe­some! Until you press back :-(

  11. Tim Mannino

    I agree with Chris Coyler. This is a great exper­i­ment but you should cer­tainly not dis­card js alto­gether in favor of a pure CSS solu­tion. JS/jQuery+CSS3 (throw HTML5 in there too) are a match made in heaven and could stand up against Flash anyday.

  12. Cristy

    Looks good after the ani­ma­tion is com­pleted, but when ani­mat­ing it’s very jaggy and doesn’t look good…

  13. Dustin Vogel

    @Cristy: It’s works very smooth in Safari… Looks great and @Jake, when you press back you get in the state before. Works perfectly.

  14. Sinan Yasar

    Great demo, thanks! Hard­ware graph­ics will change the look of web very soon i believe. (At least for mod­ern browser users)

  15. Raju

    Awesome…but there is a small prob­lem with Chrome 11 Beta.

  16. Ian P

    A great demo, thanks. After years of such sta­tic CSS, I find this :tar­get stuff decep­tively tricky and your sim­ple guide is appre­ci­ated. Nice one.

  17. Fasal Salam

    Awe­some man.
    This is a great exper­i­ment thanks!

  18. via

    nice work dude. sim­ple and beautiful

  19. Ca9ine

    This = awe­some.
    Thank heav­ens for CSS inno­va­tors like yourself!

    I was check­ing out some modal scripts, and thought to myself that this should be done with CSS3 really; I seached, and of course here it is!

    Hat-tip to you Mr. Hayes.

  20. Dan Owen

    CSS3 never fails to amaze. Thank you so much for mak­ing it all so acces­si­ble. Pity some peo­ple are using the short­com­ings of IE as an excuse not to take advan­tage of the rich offer­ings of CSS3.

    ps Love last​.fm

  21. css modal box

    This is super cool!!

  22. dalgard

    Over­lay with­out extra markup…

    I’ve also been look­ing for a way to do a semi-opaque over­lay behind the modal using gen­er­ated con­tent (:before pseudo-element).

    The trou­ble seems to be putting the modal in front; when using z-index on the modal the over­lay (which must have a neg­a­tive z-index) jumps in front for some rea­son, and with­out z-index on the modal it goes other ele­ments in the page. One could put the entire page minus the modal in a con­tainer with a z-index of –2, but this is hardly a pretty solution.

    Any­way, another way of doing a semi-opaque back­ground would be adding an extreme box-shadow like this:

    0 0 0 100000px rgba(0,0,0,0.3)

    As much as this huge pixel value hurts my eyes, my browser doesn’t seem to care at all, not using any more resources resiz­ing or doing what­ever with this behe­moth in the stylesheet.

    It makes me won­der: How bad are mon­ster val­ues in CSS with regards to per­for­mance? How wrong is it to expect the user’s screen to be less than 141,421 pix­els wide (sup­posed the modal has a border-radius)…? ;)

  23. math-loust

    love this idea
    thanks this is awesome

  24. Ashwini kumar

    This this is awesome.

  25. Ashwini kumar

    This is one of the good exam­ple to imple­ment in the my page.

  26. joa

    Great work!

    What is best way to open a modal with javascript? Adding a class with
    opac­ity: 1;pointer-events: auto;
    opens the modal, but with­out the bounce effect…