Intermediate Articles

Places It’s Tempting To Use Display: None; But Don’t

You want to hide something on a page, so:

.hide {
   display: none;
}

But wait! By applying that class to an element you've immediately made that content "inaccessible" by screen readers. You've probably known this forever, but still the poison apple sneaks into our code once in a while.

I don't want to re-hash all the specifics. Your best bet is to read "Now You See Me" by Aaron Gustafson on A List Apart to get an understanding of this if you don't already.

One way to encourage yourself to do the right thing is by creating more appropriate class names. Your regular hide class should position the content off screen, which still leaves it screen reader accessible:

.hide {
   position: absolute !important;
   top: -9999px !important;
   left: -9999px !important;
}

I use !important here because if you've gone to the trouble to add a "hide" class to something, you probably mean it and don't want to think too hard about if the specificity value is strong enough. And if you know that you need to display: none something, the class should help you understand it:

.remember-this-will-NOT-be-read {
   display: none !important;
}

Another option for accessible hiding comes from some Snook research and the HTML5 boilerplate:

.visuallyhidden { 
  position: absolute; 
  overflow: hidden; 
  clip: rect(0 0 0 0); 
  height: 1px; width: 1px; 
  margin: -1px; padding: 0; border: 0; 
}

OK you got it. Easy peasy when you're totally in control of class names and all you do is apply and remove them. But things get a little tricker with JS libraries that apply their own CSS. For instance in jQuery, after you .slideUp(), you'll have a display: none in the inline CSS to deal with. Yes, screen readers run JavaScript and yes, that's still a problem.

Again Aaron Gustafson has us covered there, who suggests applying the accessible class name after the sliding is done and then removing the display: none by sliding it the other direction.

var $button = $('#myButton'),
    $text   = $('#myText'),
    visible = true;

$button.click(function() {
  if (visible) {
    $text.slideUp('fast',function() {
      $text.addClass('hide')
           .slideDown(0);
    });
  } else {
    $text.slideUp(0,function() {
      $text.removeClass('hide')
           .slideDown('fast');
    });
  }
  visible = !visible;
});​

Here's a demo of that:

View Demo

Now we have the tools we need to stop using display: none and start using more accessible "hiding" methods.


FAQ pages

If you're hiding the answer until the question is clicked, hide with an accessible class name. Careful you don't .hide() and then slideToggle(), that's not good enough!



Labels

It's tempting to use placeholder text as a label replacement (especially now with some browsers improved UX of leaving the text until you actually type), but don't display: none or remove the labels. I recently heard a heartbreaking story about a blind girl trying to apply for college and the form had missing labels so she had no idea what to put in what fields. So if you're going to use placeholder text as a label replacement, use an accessible hiding technique for the labels.



Tabs

Just because a panel of content isn't the "currently active" one doesn't mean it should be inaccessible. Hide it with an accessible hiding technique instead. Or, you may not even need to. If all the panels are the same height, you can just flip-flop which ones is visible by adjusting z-index.



@media queries

Turning on Voice Over in OS X and using Safari is a screen reader. Now imagine that Safari window was open to a very narrow width and the page had some @media queries for handling smaller viewports. And say that @media query hides some things with display: none in order to better visually accomodate the space. This could be good or bad for accessibility. Are you hiding a bunch of crap that isn't important to the page? Or are you hiding useful things that a person using a screen reader should have access to like they normally would.


No Expert Here

This entire post is based on the premise that display: none is bad for accessibility. It's not based on my deep and thorough understanding of screen readers and general accessibility. If you have more to add, things to correct, or personal experience to share, please do.

(Better) Tabs with Round Out Borders

The following is a guest post by Menno van Slooten. You might notice we've been down this road before, but I quite like Menno's approach here. The end result proves you can get a little fancier with the design than I originally did, with borders, gradients, and shadows and while actually using less elements.

A good-looking tab control usually has one feature that I've always found impossible to reproduce without images: borders that bend to the outside at the bottom of each tab. In this article I would like to show how you can use the CSS :before and :after pseudo elements to create this effect without using images. First, let's start with some basic markup.

The markup

<ul class="tabrow">
   <li>Lorem</li>
   <li>Ipsum</li>
   <li class="selected">Sit amet</li>
   <li>Consectetur adipisicing</li>
</ul>

This would be about as basic as you can get. Not a lot of elements to work with here, but it will do.

Getting started

To get started, let's get rid of the default <ul> and <li> styling and get these babies in line.

  • Lorem
  • Ipsum
  • Sit amet
  • Consectetur adipisicing
.tabrow {
  text-align: center;
  list-style: none;
  margin: 0;
  padding: 0;
  line-height: 24px;
}
.tabrow li {
  margin: 0 10px;
  padding: 0 10px;
  border: 1px solid #AAA;
  background: #ECECEC;
  display: inline-block;
}

Selecting a tab

The selected tab should of course be clearly visible.

  • Lorem
  • Ipsum
  • Sit amet
  • Consectetur adipisicing
.tabrow li.selected {
  background: #FFF;
  color: #000;
}

Getting to the bottom of things

Every tab control needs a well-defined bottom edge. It won't do much now, but later it will help emphasize the effect of the selected tab being on top.

  • Lorem
  • Ipsum
  • Sit amet
  • Consectetur adipisicing
.tabrow {
  position: relative;
}
.tabrow:after {
  position: absolute;
  content: "";
  width: 100%;
  bottom: 0;
  left: 0;
  border-bottom: 1px solid #AAA;
  z-index: 1;
}

Here we introduced our first :after pseudo-element. Basically, :after appends another child to an element. It's not in the DOM (which is why it's called a pseudo element), so it is not a real child but it is completely stylable, as long as you add some content, in this case a single space character.

In my opinion, the terms :before and :after are slightly confusing since the pseudo's aren't actually added before or after the element they apply to, but are inserted as children. This is also why you can't apply :before and :after to elements that can't contain children ("no content" elements), like <input>.

In this case, we use the pseudo element to create a bottom border that doesn't influence the tabs' positioning. We could have just put a bottom border on the <ul> but that would've made the next step a little trickier.

Above and beyond

Now, an essential part of a convincing looking tab control, is that the selected tab sits in front of the edge while the rest fall behind the edge. To do this, we change its bottom border and do some z-index magic.

  • Lorem
  • Ipsum
  • Sit amet
  • Consectetur adipisicing
.tabrow:before {
  z-index: 1;
}
.tabrow li {
    position: relative;
  z-index: 0;
}
.tabrow li.selected {
  z-index: 2;
  border-bottom-color: #FFF;
}

Around the bends

It is now time to add the elusive border that bends to the outside and we'll use :before and :after for this. Let's take this step by step and first just put everything in position.

  • Lorem
  • Ipsum
  • Sit amet
  • Consectetur adipisicing
.tabrow li:before,
.tabrow li:after {
  position: absolute;
  bottom: -1px;
  width: 6px;
  height: 6px;
  content: " ";
}
.tabrow li:before {
  left: -6px;
}
.tabrow li:after {
  right: -6px;
}
.tabrow li:after, .tabrow li:before {
  border: 1px solid #AAA;
}

Don't be such a square

You can probably see where this is going. Let's remove the borders we don't want and add some rounded corners.

  • Lorem
  • Ipsum
  • Sit amet
  • Consectetur adipisicing
.tabrow li {
  border-top-left-radius: 6px;
  border-top-right-radius: 6px;
}
.tabrow li:before {
  border-bottom-right-radius: 6px;
  border-width: 0 1px 1px 0;
}
.tabrow li:after {
  border-bottom-left-radius: 6px;
  border-width: 0 0 1px 1px;
}

Cutting corners

There's something not quite right about this result. Let's look at it up close. As you can see both the original straight corner as well as the rounded corner are visible. We need to somehow get rid of the straight corner. To do that, we will cover it up with a shadow. To illustrate what's going on, let's make the shadow stand out a little bit.

  • Lorem
  • Ipsum
  • Sit amet
  • Consectetur adipisicing
.tabrow li:before {
  box-shadow: 2px 2px 0 red;
}
.tabrow li:after {
  box-shadow: -2px 2px 0 red;
}

Almost there

As you can see, the red shadows completely cover up the square corners we would like to hide. If we give the shadow the correct colors the illusion is complete.

  • Lorem
  • Ipsum
  • Sit amet
  • Consectetur adipisicing
.tabrow li:before {
  box-shadow: 2px 2px 0 #ECECEC;
}
.tabrow li:after {
  box-shadow: -2px 2px 0 #ECECEC;
}
.tabrow li.selected:before {
  box-shadow: 2px 2px 0 #FFF;
}
.tabrow li.selected:after {
  box-shadow: -2px 2px 0 #FFF;
}

Pieces of flair

All that's left to do now is adding a sprinkling of gradients and shadows to spice it up just a little bit.

  • Lorem
  • Ipsum
  • Sit amet
  • Consectetur adipisicing
.tabrow li {
  background: linear-gradient(to bottom, #ECECEC 50%, #D1D1D1 100%);
  box-shadow: 0 3px 3px rgba(0, 0, 0, 0.4), inset 0 1px 0 #FFF;
  text-shadow: 0 1px #FFF;
  margin: 0 -5px;
  padding: 0 20px;
}

If you're wondering about browser compatibility, it's exactly as you'd expect: everything but IE. It's very possible that it'll work in IE10, but I haven't had the chance to test with a preview release. Since IE8 and IE9 do support :before and :after but not border-radius you'll have to create a separate stylesheet for them if you want to give their users a nice visual experience.

View Demo   Download Files

Editor's note: I added anchor links inside the tabs in the demo since I think it's the most likely case that tabbed navigation like this have them. Most likely, they would have an href attribute that would link to the content they go with by id, and that behavior would be controlled by JavaScript. The fact that this tutorial doesn't need the anchor links for the extra pseudo elements is further testament to it being better than my original.

Update: Allan Watkins sent me a variation of these that point downwards:

See the Pen Round Out Tabs that Point Down by Chris Coyier (@chriscoyier) on CodePen.

Columns and the Greatest Common Divisor

I was recently putting together some CSS for columns. There was a few pre-determined widths that the columns needed to accommodate. For whatever reason (maybe because every grid framework in the world is this way) my mind went right to trying to find a common building block size. All columns are either the size of one building block or a multiple of a building block with gutter widths added. (more…)

Full Browser Width Bars

Updated on December 17, 2014 by Parker Bennett.

As the line length of text content gets longer, it can be harder to read. So it's common practice to limit the width of content as the viewport widens. Often, this is accomplished by giving the content a max-width and centering it, either within a wrapping element, or section by section.

But what if we want an element to extend visually to cover the full width of the viewport, say, a background color for a header or individual headings?

One approach is to use an inner wrapper for the content we want width-constrained, but leave the outer element at 100% width. However, this adds extra markup and it's (arguably, GASP) non-semantic!

<header>
  <div class="wrap">
    <h2>Header</h2>
  </div>
</header>

<h3 class="full-width">
  <div class="wrap">Heading</div>
</h3>
<div class="wrap">
  <p>... text ... </p>
</div>
header, .full-width {
  width: 100%;
  background: #ccc;
}
.wrap {
  width: 80%;
  max-width: 24em;
  margin: 0 auto;
  padding: 0.25em 0.625em;
}

It would be better to wrap the content semantically (e.g., main or section) and have the headings break out visually as needed. Here are a few ways we can:

Using negative margin

A simple approach (from Timothy Mackey) uses negative margins and padding to extend the background in both directions. To avoid triggering horizontal scrolling, we need to set overflow-x:hidden on both the html and body elements.

html, body {
  overflow-x: hidden;
}
.full-width-bar {
  margin: 0 -9999rem;
  /* add back negative margin value */
  padding: 0.25rem 9999rem;
  background: rgba(0, 0, 0, 0.5);
}

See the Pen Full Width Bars Using Negative Margins by Chris Coyier (@chriscoyier) on CodePen.

Want to make the full-width bar a different color than the heading background? One way is to use left and right borders. But it turns out super-wide borders have some issues. For borders over 960px, you need to make them exactly 9600px or 10240px (or equivalent ems) to avoid a bug in Chrome. Also, you need to use solid colors, since RGBa values display a tiny gap in Safari. This is because of rounding errors when the containing element is centered.

html, body {
  overflow-x: hidden;
}

.full-width-borders {
  /* also subtract section padding (1.5rem) */
  margin: 0 -601.5rem;
  /* add back section padding (1.5rem) */
  padding: 0.25rem 1.5rem;
  background: red;
  /* border has to be solid, not RGBa */
  /* 9600px or equiv (600rem = 9600/16) */
  border-left: 600rem solid maroon;
  border-right: 600rem solid maroon;
}

See the Pen Full Width Bars Using Borders by Parker Bennett (@parkerbennett) on CodePen.

Given the fussiness of using borders, let's look at an alternative:

Using pseudo elements

By adding a :before pseudo-element to the heading, you can make the background behind it distinct from the full-width bar, using absolute positioning and a negative z-index. This also lets us use RGBa for the full-width bar.

.full-width-tinted {
  position: relative; /* for child pseudo-element */
  z-index: 0;
  margin: 0 -600rem;
  /* add back negative margin value */
  padding: 0.25rem 600rem;
  background: #666;
  background: rgba(0, 0, 0, 0.25);
}

.full-width-tinted:before {
  content: "";
  position: absolute;
  z-index: -1; /* behind parent */
  top: 0;
  bottom: 0;
  /* subtract h2 padding (1.5rem) */
  left: 598.5rem;
  right: 598.5rem;
  background: green;
}

See the Pen Full Width Bars Using Negative Margins by Chris Coyier (@chriscoyier) on CodePen.

You need to be a little careful with negative z-index, since it tends to work only in fairly simple situations without many nested elements with their own backgrounds.

If you want even more flexibility, you can use two pseudo-elements, :before and :after, to extend the header background both left and right. This way the background could include different color, widths, or heights, extend in only one direction, fade with a gradient, etc.

.full-width {
  position: relative; /* for the child pseudo-elements */
  /* negative offset = section padding */
  margin: 0 -30px;
  /* add back section padding value */
  padding: 0.25rem 30px;
  background: #333;
}
.full-width:before, .full-width:after {
  content: "";
  position: absolute;
  /* fill vertically */
  top: 0;
  bottom: 0;
  width: 9600px;
  right: 100%;
  background: green;
}
.full-width:after { 
  width: 320px;
  left: 100%;
  /* you're free to do something totally different here */
  background: green;
}

See the Pen Full Width Bars Using Pseudo-Elements by Chris Coyier (@chriscoyier) on CodePen.

Using box-shadow

A clever way to not trigger horizontal scrolling is to use box-shadow to generate the background extensions (at least on the right). Andreas created a demo of this:

See the Pen Full Width Browser Bars with box-shadow by Andreas (@receter) on CodePen.

Unfortunately, there are issues with this. Charles Stuart pointed out a bug in Internet Explorer 11 (v11.0.9600.17501) that makes using box-shadow unreliable. It appears IE is using sub-pixel antialiasing on the box-shadow even when blur is set to 0.

Using viewport units

Lisa suggested a way to not trigger horizontal scrolling by using a combination of vw and calc(), which now have pretty solid browser support. It could be useful in the right circumstance (but doesn't yet factor in things like padding or min-width).

See the Pen Full width bars by Chris Coyier (@chriscoyier) on CodePen.

Case Study: jQuery Fixer Upper

Recently in the CSS-Tricks Forums, user named Waffle posted the following bit of jQuery JavaScript:

$(function(){

	$('.ContactArea').hide();
                
	$('.Portfolio').hide();
                
	$('.WebDesign').hide();

	$('.AboutCoadin').hide();

	$('li.Contact').click(function(){
		$(".ContactArea").slideToggle();
	});

	$('li.PortfolioBtn').click(function(){
		$(".Portfolio").slideToggle();
	});

	$('li.WebDesignBtn').click(function(){
		$(".WebDesign").slideToggle();
	});

	$('li.AboutBtn').click(function(){
		$(".AboutCoadin").slideToggle();
	});
                        
});

This user's actual question was how they can extend their code to add functionality to scroll the page down to the areas as they expand. But in looking at code like this with a slightly more experienced eye, there are things that stick out as things that we can likely improve upon. Since I bet there are some of you folks that are at Waffle's level and writing code like this, I thought we could use this as a case study to learn from.

It works, but...

To start with, Waffle's code isn't broken, it works fine and because of it's simple nature the improvements that we'll end up making probably won't even make noticeable speed improvements. However, the improvement we make should make the code more readable, more efficient, more extensible, and improve other related things. Also, understanding the improvements we make will make us better coders in the future when the stakes are higher.

Do we need a DOM ready statement?

The first line of code is this:

$(function(){

...which is jQuery shorthand for "When the DOM is ready to manipulated, run this function..." This is a safety net so that, for example, selectors aren't run on HTML that isn't parsed yet, leaving us wondering WTF. It also doesn't wait until the page has fully loaded, which would make our script appear sluggish.

But the thing is, we should be running scripts like this at the bottom of our pages (before the closing </body> tag), so that loading the script doesn't hold up page rendering. If we load scripts properly at the bottom of the page, a DOM ready statement isn't needed, as the browser has dealt with all that HTML before it has even started reading the script.

Multiple Selectors, One jQuery Object

The next lines of code are:

$('.ContactArea').hide();             
$('.Portfolio').hide();     
$('.WebDesign').hide();
$('.AboutCoadin').hide();

Creating four separate jQuery objects here is unnecessary, as you can use multiple selectors in one. This does the same thing:

$(".ContactArea, .Portfolio, .WebDesign, .AboutCoadin").hide();

Just like a CSS selector. However, you might actually want four separate jQuery objects, so that you can refer to them later without needing to create them new. In that case, set variables:

var contactArea = $('.ContactArea').hide(),
    portfolio   = $('.Portfolio').hide(),
    webDesign   = $('.WebDesign').hide(),
    about       = $('.AboutCoadin').hide();

You might also consider adding a class name to the HTML like "initial-hide" and applying it to all elements you wish to hide at time of page load and selecting by that, but that may walk on the wrong side of the semantics line.

ID's

It's hard to say without seeing the real website, but I have a feeling that these areas like "contact area" and "portfolio" are totally unique areas on the page, not things that are repeated more than once. Totally unique areas are ideal for using ID's (as opposed to classes). The benefit here being that ID selectors are faster.

$("#ContactArea") // is faster than
$(".ContactArea")

The other important things about ID's is that they are natural jump-links, which work regardless of JavaScript being enabled:

<!-- This link will jump down the page ... -->
<a href="#ContactArea">Contact</a>

<!-- ... to ensure this element is visible -->
<section id="ContactArea"></section>

You may not actually want the page to jump down, but you can stop that with JavaScript by preventing default behavior. Without JavaScript, you definitely want same-page navigation to be jump links.

Select the appropriate element

The next bit of code has four repetitive bits:

$('li.Contact').click(function(){
	$(".ContactArea").slideToggle();
});

$('li.PortfolioBtn').click(function(){
	$(".Portfolio").slideToggle();
});

$('li.WebDesignBtn').click(function(){
	$(".WebDesign").slideToggle();
});

$('li.AboutBtn').click(function(){
	$(".AboutCoadin").slideToggle();
});

The first thing to discuss there is the actual element being selected. Presumably, the markup is something like this:

<nav>
  <ul>
     <li class="ContactArea"><a href="#">Contact</a></li>
     <li class="PortfolioBtn"><a href="#">Contact</a></li>
     <li class="WebDesignBtn"><a href="#">Contact</a></li>
     <li class="AboutBtn"><a href="#">Contact</a></li>
  </ul>
</nav>

The jQuery code is selecting the list items by their class names. These are my issues with that:

  1. They don't all use the "Btn" convention. If you stick with classes like this, you might as well make it standard. Not sticking to it in this tight of quarters would be symptomatic of other, larger inconsistencies.
  2. You don't need the classes for CSS to style them uniquely. You can use :nth-child or :nth-of-type. Unless you need to support super old browsers, in which case, you are using jQuery anyway and could feign support with that.
  3. Because you don't need them for styling, I'd say you don't really need them at all.

Assuming the markup can be changed to use ID's instead of classes, the HTML could be rewritten like this:

<nav>
  <ul>
     <li><a href="#ContactArea">Contact</a></li>
     <li><a href="#Portfolio">Portfolio</a></li>
     <li><a href="#WebDesign">Web Design</a></li>
     <li><a href="#AboutCoadin">About</a></li>
  </ul>
</nav>

This is a more semantic representation of navigation. No unneccesary classes and anchor links that are same-page links to the appropriate content.

Don't Repeat Yourself

Which is commonly referred to as "DRY." The four statements that do all that selecting and slideToggling are very repetitive. We can improve upon that with our new and improved HTML structure.

The href attribute for those anchor links in the navigation are like "#Contact", which is exactly what we need as a selector string to find that element. Super convenient! Let's yank that href from the link and use it as a selector, that way one single click handler can handle all four of our links:

$("nav a").click(function(e){
  e.preventDefault();
  $($(this).attr("href")).slideToggle();
});

Final Code

Our final code ends up being much more succinct:

$("#ContactArea, #WebDesign, #Portfolio, #AboutCoadin").hide();

$("nav a").click(function(e){
  e.preventDefault();
  $($(this).attr("href")).slideToggle();
});

View Demo

To actually answer Waffle's question

The posed question was actually "I also want the page to scroll to those divs when those buttons are pressed" To which I would say, are you sure? That seems like a weird combination of animated activity. Smooth scrolling same-page links are actually a little more complicated than you might think, but I have some great copy-and-pasteable code for them here. The problem is that it won't quite work in this scenario, because at the time the link is clicked the element to scroll to is actually hidden. It's not until the slideToggle is done where the element is unhidden and at it's final size. You'd have to delay the page scroll until the callback function for that. Doable, but janky.

Did I do OK?

One of my favorite parts about publishing blog posts on this site is that people really take the time to read it and offer quality suggestions. If you have some even better ways at handling the things I suggested above, I'd love to hear it!

Styling Texty Inputs Only

Let's say you want fancy styling your your text inputs:

So you go and style those inputs:

input {
    border: 5px solid white; 
    -webkit-box-shadow: 
      inset 0 0 8px  rgba(0,0,0,0.1),
            0 0 16px rgba(0,0,0,0.1); 
    -moz-box-shadow: 
      inset 0 0 8px  rgba(0,0,0,0.1),
            0 0 16px rgba(0,0,0,0.1); 
    box-shadow: 
      inset 0 0 8px  rgba(0,0,0,0.1),
            0 0 16px rgba(0,0,0,0.1); 
    padding: 15px;
    background: rgba(255,255,255,0.5);
    margin: 0 0 10px 0;
}

But then you add a few more inputs, a file input and the submit button, and you get sad face because you didn't want those styles to affect those elements.

So then you are like no-prob-bob, I'll just make sure to only style text inputs!

input[type=text] {
   /* Awesome styling */
}

But then you realize as you build out your form, you want to make use of the new HTML5 input types. Those new input types mean better accessibility, better mobile support, and enhanced desktop browser experiences. There is type=email, type=url, type=tel, type=number, type=color, and a bunch more! This is going to get cumbersome...

input[type=text],
input[type=url],
input[type=tel],
input[type=number],
input[type=color],
input[type=email], {
   /* And that ain't all of them... */
}

What are we to do? Well my favorite way would be to harness CSS3 and the :not() selector. So instead of including all the new types, we exclude the ones we don't want styled.

input:not([type=submit]):not([type=file]) {
   /* omg so much cleaner */
}

Now you're back to square one (default user agent stylesheet) on those other inputs. Don't be afraid to use browser default form controls, people know them and are used to them.

But hold on there hotpants. IE doesn't support that :not() selector until version 9, which isn't even really out yet. Yes indeed, and that sucks. However, if you want to polyfix that sucka you could do so with a quick copy-and-paste job.

<!--[if lt IE 9]>
<script src="//ie7-js.googlecode.com/svn/version/2.1(beta4)/IE9.js"></script>
<![endif]-->

That is from this project which:

Upgrade[s] MSIE5.5-8 to be compatible with modern browsers.

Not interested in that? Well you could always just list out all the attribute selectors like I listed above, which will get you back to IE7 support. Or, you could go back to just adding class names onto every single input and selecting by that which should take you back to the beginning of the internet.

Keep Margins Out of Link Lists

When building a menu or other list of links, it's generally a good practice to use display: block; or display: inline-block; so that you can increase the size of the link target. The simple truth: bigger link targets are easier for people to click and lead to better user experience.

(more…)

Perfect Full Page Background Image

This post was originally published on August 21, 2009 and is now being being republished as it has been entirely revised. Both original methods are removed and now replaced by four new methods.

The goal here is a background image on a website that covers the entire browser window at all times. Let's put some specifics on it:

  • Fills entire page with image, no white space
  • Scales image as needed
  • Retains image proportions (aspect ratio)
  • Image is centered on page
  • Does not cause scrollbars
  • As cross-browser compatible as possible
  • Isn't some fancy shenanigans like Flash

Image above credited to this site.

Awesome, Easy, Progressive CSS3 Way

We can do this purely through CSS thanks to the background-size property now in CSS3. We'll use the html element (better than body as it's always at least the height of the browser window). We set a fixed and centered background on it, then adjust it's size using background-size set to the cover keyword.

html { 
  background: url(images/bg.jpg) no-repeat center center fixed; 
  -webkit-background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
  background-size: cover;
}

Works in:

  • Safari 3+
  • Chrome Whatever+
  • IE 9+
  • Opera 10+ (Opera 9.5 supported background-size but not the keywords)
  • Firefox 3.6+ (Firefox 4 supports non-vendor prefixed version)

View Demo

Update: Thanks to Goltzman in the comments for pointing out an Adobe Developer Connection article which features some code to make IE do cover backgrounds as well:

filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='.myBackground.jpg', sizingMethod='scale');
-ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='myBackground.jpg', sizingMethod='scale')";

But careful, reader Pierre Orsander said they tried this and had some problems with links on the page going dead.

Update: Matt Litherland writes in to say that anyone trying to use the above IE filters and having problems with scrollbars or dead links or whatever else (like Pierre above) should try NOT using them on the html or body element. But instead a fixed position div with 100% width and height.

CSS-Only Technique #1

Big thanks, as usual, to Doug Neiner for this alternate version. Here we use an inline <img> element, which will be able to resize in any browser. We set a min-height which keeps it filling the browser window vertically, and set a 100% width which keeps it filling horizontally. We also set a min-width of the width of the image so that the image never gets smaller than it actually is.

The especially clever bit is using a media query to check if the browser window is smaller than the image, and using a combo percentage-left and negative left margin to keep it centered regardless.

Here is the CSS:

img.bg {
  /* Set rules to fill background */
  min-height: 100%;
  min-width: 1024px;
	
  /* Set up proportionate scaling */
  width: 100%;
  height: auto;
	
  /* Set up positioning */
  position: fixed;
  top: 0;
  left: 0;
}

@media screen and (max-width: 1024px) { /* Specific to this particular image */
  img.bg {
    left: 50%;
    margin-left: -512px;   /* 50% */
  }
}

Works in:

  • Any version of good browsers: Safari / Chrome / Opera / Firefox
  • IE 6: Borked - but probably fixable if you use some kind of fixed positioning shim
  • IE 7/8: Mostly works, doesn't center at small sizes but fills screen fine
  • IE 9: Works

View Demo

CSS-Only Technique #2

One rather simple way to handle this is to put an inline image on the page, fixed position it to the upper left, and give it a min-width and min-height of 100%, preserving it's aspect ratio.

<img src="images/bg.jpg" id="bg" alt="">
#bg {
  position: fixed; 
  top: 0; 
  left: 0; 
	
  /* Preserve aspet ratio */
  min-width: 100%;
  min-height: 100%;
}

However, this doesn't center the image and that's a pretty common desire here... So, we can fix that by wrapping the image in a div. That div we'll make twice as big as the browser window. Then the image will be placed, still preserving it's aspect ratio and covering the visible browser window, and the dead center of that.

<div id="bg">
  <img src="images/bg.jpg" alt="">
</div>
#bg {
  position: fixed; 
  top: -50%; 
  left: -50%; 
  width: 200%; 
  height: 200%;
}
#bg img {
  position: absolute; 
  top: 0; 
  left: 0; 
  right: 0; 
  bottom: 0; 
  margin: auto; 
  min-width: 50%;
  min-height: 50%;
}

Credit to Corey Worrell for the concept on this one.

Works in:

  • Safari / Chrome / Firefox (didn't test very far back, but recent versions are fine)
  • IE 8+
  • Opera (any version) and IE both fail in the same way (wrongly positioned, not sure why)
  • Peter VanWylen wrote in to say that if you add the image via JavaScript, the img needs to have width: auto; and height: auto; to work in IE 8, 9, or 10.

View Demo

jQuery Method

This whole idea becomes a lot easier (from a CSS perspective) if we know if the aspect ratio of the image (inline <img> we intend to use as a background) is larger or smaller than the current aspect ratio of the browser window. If it is lower, than we can set only the width to 100% on the image and know it will fill both height and width. If it is higher, we can set only the height to 100% and know that it will fill both the height and width.

We have access to this information through JavaScript. As usual around here, I like to lean on jQuery.

<img src="images/bg.jpg" id="bg" alt="">
#bg { position: fixed; top: 0; left: 0; }
.bgwidth { width: 100%; }
.bgheight { height: 100%; }
$(window).load(function() {    

	var theWindow        = $(window),
	    $bg              = $("#bg"),
	    aspectRatio      = $bg.width() / $bg.height();
	    			    		
	function resizeBg() {
		
		if ( (theWindow.width() / theWindow.height()) < aspectRatio ) {
		    $bg
		    	.removeClass()
		    	.addClass('bgheight');
		} else {
		    $bg
		    	.removeClass()
		    	.addClass('bgwidth');
		}
					
	}
	                   			
	theWindow.resize(resizeBg).trigger("resize");

});

This doesn't account for centering, but you could definitely alter this to do that. Credits to Koen Haarbosch for the concept behind this idea.

Works in:

  • IE7+ (could probably get in IE6 with a fixed position shim)
  • Most any other desktop browser

View Demo

Update (June 2012): Reader Craig Manley writes in with a technique to load an appropriately sized background image according to screen. As in, don't load some huge 1900px wide background image for an iPhone.

First, you'd make images like 1024.jpg, 1280.jpg, 1366.jpg, etc. Then instead of loading an img, you'd load a shim.

<img id="bg" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="" style="position: fixed; left: 0; top: 0" />

If you don't like the gif shim (personally I think it's OK because it's not "content" it's a background) you could load up one of the real images instead. This code will account for that.

Then you test the screen width and set the src of the image based on it. The code below does it on resize, which you may or may not want. You could just run the code once if you wanted.

(function() {

var win = $(window);

win.resize(function() {
    
    var win_w = win.width(),
        win_h = win.height(),
        $bg    = $("#bg");

    // Load narrowest background image based on 
    // viewport width, but never load anything narrower 
    // that what's already loaded if anything.
    var available = [
      1024, 1280, 1366,
      1400, 1680, 1920,
      2560, 3840, 4860
    ];

    var current = $bg.attr('src').match(/([0-9]+)/) ? RegExp.$1 : null;
    
    if (!current || ((current < win_w) && (current < available[available.length - 1]))) {
      
      var chosen = available[available.length - 1];
      
      for (var i=0; i<available.length; i++) {
        if (available[i] >= win_w) {
          chosen = available[i];
          break;
        }
      }
      
      // Set the new image
      $bg.attr('src', '/img/bg/' + chosen + '.jpg');
      
      // for testing...
      // console.log('Chosen background: ' + chosen);
      
    }

    // Determine whether width or height should be 100%
    if ((win_w / win_h) < ($bg.width() / $bg.height())) {
      $bg.css({height: '100%', width: 'auto'});
    } else {
      $bg.css({width: '100%', height: 'auto'});
    }
    
  }).resize();
  
})(jQuery);

Note that screen width isn't the only possible good information to have when choosing an image size. See this article.

Enjoy

If you use this, please feel free to leave what technique you used and if you altered it in any way in the comments below. Always cool to see techniques "in the wild."

Download Files

Just for posterity's sake, there is another example in here called table.php which uses an old technique that used to be a part of this article. It had some cleverness, but wasn't quite as good as either CSS technique now presented above.

Other Resources

  • jQuery plugin: Vegas, by Jay Salvat

Transparent Borders with background-clip

Have you ever seen an element on a page with transparent borders? I think Facebook originally popularized it giving birth to lightbox plugins like Facebox. I don't think Facebook sports the look anymore, but it's still rather neat.

You might think it would be as simple as this:

#lightbox {
   background: white;
   border: 20px solid rgba(0, 0, 0, 0.3);
}

However, setting a transparent border on an element will reveal the elements own background underneath the border.

In the screenshot above, the borders use RGBa to be transparent, but they appear solid gray because they are only revealing the solid white background of itself below.

Fortunately there is a CSS3 property to save us! It's called background-clip and it's used specifically to specify which portion of the box model should be utilized to display the background. It does what it sounds like it does, it cuts off the background at the specified portion of the box. There are three values it can have, and vendor prefixes do get involved. Here are the three settings it could have. You wouldn't use them all at once, this is for convenience of displaying only:

#lightbox {

  -moz-background-clip: border;     /* Firefox 3.6 */
  -webkit-background-clip: border;  /* Safari 4? Chrome 6? */
  background-clip: border-box;      /* Firefox 4, Safari 5, Opera 10, IE 9 */
				
  -moz-background-clip: padding;     /* Firefox 3.6 */
  -webkit-background-clip: padding;  /* Safari 4? Chrome 6? */
  background-clip: padding-box;      /* Firefox 4, Safari 5, Opera 10, IE 9 */
				
  -moz-background-clip: content;     /* Firefox 3.6 */
  -webkit-background-clip: content;  /* Safari 4? Chrome 6? */
  background-clip: content-box;      /* Firefox 4, Safari 5, Opera 10, IE 9 */
				
}

Here are the schematics:

So I'm sure you see where I'm going here... if we set the background-clip to the padding-box, the white background will end before the border and the transparency will lay over other content acheiving the look we are going for!


View Demo

Related: background-origin

In our lightbox example, it's most likely that the background is a solid color. In that case, background-origin is rather irrelevant, as nobody will ever be able to tell where the color "started" from. However, if the background of the element is an image, it can be quite important where the origin point of the background starts.

This is related to background-clip, because if the background-clip is the padding-box but the background-origin is left at the default border-box, some of the background-image will be cut off, which may or not be desireable.

Here is the schematic:

And a visual example:

Browser Compatibility

Works in: Safari 5, Chrome 7, Firefox 3.6+, Opera 10, IE 9

I only tested these modern browsers and support is good. It may (and likely does) go back a bit further in the non-IE browsers.

If you'd more complete browser compatibility, you can always go with the double-div method.

<div id="lightbox">
   /* Set transparent background with PNG
       add padding to push inside box inward */

   <div id="lightbox-inside">
      /* Set white background in here */
   </div>

</div>

Another advantage to the double-div method is that you could achieve truly rounded borders. Unfortunately with the background-clip method, the outer border is round but the edge of the background stays straight-cornered.

Too bad we don't have something like background-radius to fix it:

#lightbox
   border-radius: 16px;
   padding: 8px;

   /* NOT REAL */
   background-radius: 8px;
}

Weirdnesses

20px of border was fine, but when I tried 30px, these mini boxes of death showed up in Safari 5.

In Chrome, little diagonal lines were present at any border width up to 20px.

Above 20px border, the corners completely darken.

Without border-radius, darkened corner boxes are always visible.

Thanks

Thanks to Alex Hall for the original idea and help.

Here's a simple demo showing the different values of background-clip as well:

Check out this Pen!

and background-origin:

Check out this Pen!

Adding Stroke to Web Text

Fonts on the web are essentially vector based graphics. That's why you can display them at 12px or 120px and they remain crisp and relatively sharp-edged. Vector means that their shape is determined by points and mathematics to describe the shape, rather than actual pixel data. Because they are vector, it would make sense if we could do things that other vector programs (e.g. Adobe Illustrator) can do with vector text, like draw a stroke around the individual characters. Well, we can! At least in WebKit. Example:

h1 {
   -webkit-text-stroke-width: 1px;
   -webkit-text-stroke-color: black;
}

Or shorthand:

h1 {
   -webkit-text-stroke: 1px black;
}

Right away, I'm sure you are thinking: "Cool, but only WebKit supports, this, so if I set my text color to white and my background is white, the stroke makes it look cool in WebKit but disappears in other browsers!"

WebKit has your back on that one, you can set the text color with another proprietary property, so you're safe for all browsers:

h1 {
   color: black;
   -webkit-text-fill-color: white; /* Will override color (regardless of order) */
   -webkit-text-stroke-width: 1px;
   -webkit-text-stroke-color: black;
}

Shown here with @font-face font Anime Ace 2 by Nate Piekos:

Properly stroked!

Fallback to solid color. Shown here in Firefox

Simulation

We can take this a bit further by not relying on the WebKit proprietary entirely. We can use the text-shadow property (supported in Firefox, Opera, and IE 10 as well) and simulate a stroke. We'll use four shadows, each 1px offset of black with no spread, one each to the top right, top left, bottom left, and bottom right. We'll use remove the WebKit propreitary -webkit-text-fill-color in favor of color since we're cross-browser compatible now. The only holdout would be IE9 and down, which of course you can use IE specific stylesheets to account for.

h1 {
  color: white;
  text-shadow:
   -1px -1px 0 #000,  
    1px -1px 0 #000,
    -1px 1px 0 #000,
     1px 1px 0 #000;
}

This is a stroke using all text-shadow. Pretty close to just as good as a real stroke. The primary issue is that you can only get 1px of stroke this way. Any more, and you see gaps. Any more with WebKit text stroke and there is issues too though, so it's kind of a horse apiece.

Combining

Using both a stroke and a shadow can be a great effect. Let's toss on a WebKit stroke, the all-around text-shadow stroke, as well as a deeper text-shadow stroke.

h1 {
   -webkit-text-stroke: 1px black;
   color: white;
   text-shadow:
       3px 3px 0 #000,
     -1px -1px 0 #000,  
      1px -1px 0 #000,
      -1px 1px 0 #000,
       1px 1px 0 #000;
}

Lookin' good

Alignment

If you are familiar with Adobe Illustrator, you may know that you can align a stroke on the inside of a shape, outside, or centered. That option looks like this in the palette:

From left to right: center, inside, outside

For reasons unbeknownst to me, text in Illustrator can only be set to center stroke alignment as well. Once you expand the text into regular vector paths though, all three options become available. Reminder from Sam Frysteen: add a new stroke in the appearance panel and move it below your text (basically mimics outside stroke alignment).

From top to bottom: inside, centered, outside.

Only outside text stroke alignment looks any good at all to me. It's unfortunate, both for CSS and for Illustrator, that the unchangeable default is centered. The solution is just not to go too crazy with the thickness of your stroke border and things should be OK. Note: the text-shadow-only solution doesn't have this problem, but also is unable to stroke any more than 1px.

If you use a pseudo element, you can stroke the same text behind the original text and kinda fake outside stroke.

What we can't do

There are other things that vector based graphics programs can do with text. You can squish the letter horizontally / stretch them vertically. This type of text treatment is almost universally frowned upon, so no big loss that we can't do that. You can also set type on an irregular line (like around a circle). It certainly would be cool to do this with web text. Perhaps we could set text to follow the border path of its parent element.

p.circular {
  width: 200px;
  height: 200px;
  border-radius: 100px;
  
  /* NOT REAL */
  text-align: border-path;
}

In Illustrator, we can also tell a stroke how to handle sharp corners: rounded, beveled, or mitered. These can have nice effects, are not possible on the web. However, the WebKit handling of corners is pretty nice at its default (whatever it is), and arguably nicer than any of the options in Illustrator.

Fancies

For the record, you can use any type of color value for the color of WebKit stroke (hex, rgba, hsla, keyword). That means transparent strokes if you want them, which indeed to "stack" in that if strokes overlap each other (common) they will be darker.

As far as WebKit keyframe animation, the stroke color will animate but the stroke width will not.

/* Only the color will change, not the width */
@-webkit-keyframes colorchange {
	0% {
		-webkit-text-stroke: 10px red;
	}
	100% {
		-webkit-text-stroke: 20px green;
	}
}

Demo & Download

View Demo   Download Files

Thanks

Thanks to Hendra Susanto and Cory Simmons for sending in ideas and demos.

We have a pretty good* newsletter.