Dealing with browser inconsistencies often makes up a majority of the work for a web designer. Sometimes there is no reasonable way to accomplish a desired layout in all major web browsers without the use of some special exception rules for certain layout engines. Hacks necessarily lead to potential complications and should be avoided whenever possible, but when the circumstances require hacks to be used, it’s best to know what your options are and weigh the consequences appropriately. The purpose of this article is to describe some of the CSS hacks, also called CSS filters, with the least significant potential consequences.
Table of Contents
Conditional comments
UpDue to its relatively poor level of standards support, Internet Explorer tends to be the subject of most CSS hacks. Luckily, as of version 5, it deliberately supports a rather safe-to-use hack called “conditional comments”. Conditional comments are specially constructed HTML comments that Internet Explorer on Windows may treat differently from other browsers, optionally based on IE’s version number. They can cause Internet Explorer to ignore the markup between comments or to include part of a comment as if it was regular markup. Conditional comments apply specifically to browsers using Internet Explorer’s Trident layout engine, meaning IE-based browsers like Maxthon and Avant handle them like Internet Explorer does while browsers using other layout engines see them simply as regular comments. Internet Explorer on the Mac uses a different layout engine and doesn’t support conditional comments.
The most beneficial aspect of conditional comments is that you are not relying on browser bugs when using them. When you use CSS hacks that rely on browser bugs, you run into the possibility of those bugs being fixed at an unwanted time or other browsers showing the same bugs. Conditional comments only work in browsers that specifically support them and claim to be based on Internet Explorer, which in this case all known browsers are honest about.
There are two forms of conditional comments: positive and negative. A positive conditional comment will expose the included markup only to web browsers that match the condition (meaning only the selected versions of Internet Explorer). A negative conditional comment will expose the markup only to web browsers that don’t match the condition (meaning all non-IE web browsers and any versions of IE that the condition didn’t match). Note that, since versions of IE older than IE 5 don’t support conditional comments, you may get unexpected results in those browsers.
Syntax
UpThe syntax for conditional comments is as follows:
-
Positive
-
<!--[if condition]> HTML <![endif]–>
Negative
-
<!--[if !condition]><![IGNORE[-->
<![IGNORE[]]>
HTML<!--<![endif]–>
condition is one of the following:
- Any version of IE
- Versions less than version
- Versions less than or equal to version
- Only version version
- Versions greater than or equal to version
- Versions greater than version
IE
lt IE version
lte IE version
IE version
gte IE version
gt IE version
version is the version of Internet Explorer, typically 5
, 5.5
, 6
, or 7
HTML is the HTML to be included if the condition does or doesn’t match, depending on the type of conditional comment. When included, the HTML is placed right where the conditional comment is in the source.
For negative conditions,
can be replaced with <![IGNORE[-->
<![IGNORE[]]>
if the condition is simply -->
IE
. The longer version is only needed when Internet Explorer might parse the contents.
The
directive is not available in XML, so it is illegal to use it in XHTML. A solution would be to split it up into two special conditional comments: <![IGNORE[ ... ]]>
where XHTML is the same both places. Note that Internet Explorer 7 and below don’t yet recognize XHTML as a form of XML, so this is merely forward-looking.<!--[if !condition]> XHTML <![endif]–>
<!--[if !IE]>–>
XHTML <!--<![endif]–>
To minimize the chance of your site breaking in future versions of Internet Explorer, read Preparing your site for IE.next.
Fixing stand-alone versions of Internet Explorer
UpInternet Explorer was not designed to allow multiple versions to be installed at once, and Microsoft doesn’t officially support any such configurations. If you use one of the hacked third party packages that attempts to do this, you will experience problems with version-specific conditional comments, among other things. This is because the different stand-alone copies still rely on a common centralized registry for certain data, including version information.
Although there is no simple way to cut through all of the issues with stand-alone versions of Internet Explorer, it is possible to force them to look elsewhere for their version information, thus fixing this issue with conditional comments. The trick is to remove the normal centralized version indicator. To do this, first open up regedit.exe
from the “Run…” dialog. Navigate to HKEY_LOCAL_MACHINE/Software/Microsoft/Internet Explorer/Version Vector/
(If HKEY_LOCAL_MACHINE
doesn’t exist, try HKLM
instead). In the right pane, you should see a row with a Name value of IE
. Rename this by clicking on it and changing it to zIE (or anything unique and different). Restart Internet Explorer to see the effects. Now when it looks for the IE
key for its version information, the key will be missing and it will be forced to determine the correct version number from its own module.
Stand-alone versions of Internet Explorer have a number of other issues, and it therefore may be better to instead use a separate virtual machine for each version of Internet Explorer to ensure that what you see is what your users will see. I recommend VMware Server, which is completely free of charge and fairly easy to set up.
Conditional comments as a CSS hack
UpConditional comments can be used as a CSS hack by including links to stylesheets based on the layout engine. Here is an example of how stylesheets can be separated in this way:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<title>Test</title>
<link href="all_browsers.css" rel="stylesheet" type="text/css">
<!--[if IE]> <link href="ie_only.css" rel="stylesheet" type="text/css"> <![endif]-->
<!--[if lt IE 7]> <link href="ie_6_and_below.css" rel="stylesheet" type="text/css"> <![endif]-->
<!--[if !lt IE 7]><![IGNORE[-->
<![IGNORE[]]>
<link href="recent.css" rel="stylesheet" type="text/css"> <!--<![endif]-->
<!--[if !IE]>-->
<link href="not_ie.css" rel="stylesheet" type="text/css"> <!--<![endif]-->
</head>
<body>
<p>Test</p>
</body>
</html>
In the above example, all_browsers.css
applies to all browsers, ie_only.css
only applies to all versions of Internet Explorer, ie_6_and_below.css
applies to all versions of Internet Explorer below IE 7, recent.css
applies to all browsers except IE versions below 7, and not_ie.css
applies to all non-IE browsers.
See also: MSDN: About Conditional Comments
In-CSS hacks
UpOne of the drawbacks of conditional comments is that they require changes to the HTML source. Unfortunately, there is no equivalent to conditional comments in CSS. Instead, if you must use in-CSS hacks, you must use some other much less reliable techniques, often involving the exploitation of browser bugs.
Easy selectors
UpMost in-CSS hacks deal with selector bugs. The following is a list of browser version ranges and the beginnings of selectors that are known to select elements in them. Note that because these hacks rely on browser bugs or missing features, results may vary in some lesser-known or future browsers. All of these selectors use valid CSS.
-
IE 6 and below
-
* html {}
IE 7 and below
-
*:first-child+html {} * html {}
IE 7 only
-
*:first-child+html {}
IE 7 and modern browsers only
-
html>body {}
Modern browsers only (not IE 7)
-
html>
/*
*/
body {}
Recent Opera versions 9 and below
-
html:first-child {}
Note that the hack for IE 7 and below is actually two separate selectors: one for IE 7 and one for IE 6 and below. The rest of the desired selector must be added to both parts of the hack. The two parts cannot be combined with a comma, because IE 6 and below will fail to correctly parse the selector and won’t be targetted.
Some of these selectors require that the document has a doctype but no processing instructions (including XML declarations). This is the ideal setup to prevent IE 6 from going into quirks mode anyway.
The above selectors will select either the html
or body
element. This should be used as the start of your full selector. For example, if your desired selector is #foo .bar
and you want it to apply only to IE 7, your resulting selector will be *:first-child+html #foo .bar
.
Warning: Due to the nature of the Opera-specific selector and Internet Explorer 7’s incorrect handling of :first-child
, it is very possible that the html:first-child
selector may also select in a future version of Internet Explorer, so be careful when using it. This selector also relies on a bug, so it may be fixed in a future version of Opera. This page also describes an alternative method that is more of an issue to implement but may be somewhat more dependable considering the likely priorities of bug fixing.
Minimized attribute selectors
UpThese hacks are based on differences in handling of attributes in minimized form. If a tag is written <input disabled>
, input[disabled="disabled"] {}
should select it. However, most browsers get this wrong and in different ways.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<title>Test</title>
</head>
<body>
<input type="hidden" disabled id="attrhack">
<p>Test</p>
</body>
</html>
For the above markup, here are the selectors various browsers recognize to select the p
element:
- Firefox 1.5 and below, possibly future versions
- Safari 2.0 and below, possibly future versions
- Konqueror 3.5 and below, possibly future versions
- Opera 9 and below, possibly future versions
#attrhack[disabled=""]+p {}
#attrhack[disabled="true"]+p {}
Note that neither of these selects Internet Explorer 7. Although it supports attribute selectors and adjacent sibling combinators, it doesn’t seem to recognize a string value for attributes in minimized form.
Notice: Minimized attribute form is allowed in HTML but not in XHTML. This hack will not work in XHTML documents.
!important
UpInternet Explorer 6 and below had a problem with the !important
identifier that caused it to be ignored if another declaration of the same property appeared later in the same style declaration block. This can be used to feed Internet Explorer 6 and below special property values that are ignored by other browsers. Internet Explorer 7 fixed this issue.
Here is an example of this technique in use:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> <head> <title>Test</title> <style type="text/css">
p { background: green !important;
/* Major browsers other than IE 6 and below respect the importance immediately */
background: red;/* IE 6 and below use this value instead, even though the above was marked as important */
}</style> </head> <body class="page-body"> <p>Test</p> </body> </html>
@import “non-ie.css” all;
UpInternet Explorer 7 and below don’t support media selectors on @import
rules, instead ignoring the entire rule when they are present. Therefore, you can create an entire stylesheet for non-IE browsers and import it into your main stylesheet by adding @import "non-ie.css" all;
.
Future versions of Internet Explorer may support the @import
rule correctly.
@import "stylesheet.css” all;
imports the stylesheet in all major browsers except IE 7 and below. It may or may not work in future versions of IE.
body[class|="page-body"]
UpThe CSS 2.1 specification isn’t clear about whether or not a hyphen can be included in the value of a hyphen-separated attribute selector. Most browsers, including Firefox and Internet Explorer 7, Allow the body[class|="page-body"]
selector to select an element whose start tag looks like this: <body class="page-body">
. However, Opera interprets the specification differently in this regard. It splits up the attribute value by hyphens and only checks the first piece against the attribute selector value. Obviously, if the attribute was split by hyphens, the first piece won’t have any hyphens in it, so Opera treats this selector as a non-match. Therefore, when the proper class is applied to the body
element, this selector matches Internet Explorer 7 and most modern browsers except Opera. Opera may change their behavior to match other browsers in the future, but this technique is known to work for Opera 8 and 9.
Here is an example of this technique in use:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> <head> <title>Test</title> <style type="text/css">
p { background: red;
/* Applies to all major browsers */
} body[class|="page-body"] p { background: green;/* Applies to IE 7 and most modern browsers except Opera */
}</style> </head> <body class="page-body"> <p>Test</p> </body> </html>
body[class|="page-body"] {}
selects the body
element with the class page-body
in IE 7 and all modern browsers except Opera 9 and below. It may or may not work in future versions.
Unrecommended hacks
UpIf you are going to use hacks, the above techniques are the recommended choices. However, it’s interesting to point out the following unrecommended hacks. Some of them rely on invalid CSS or are more clumsy than the above alternatives.
_property: value and -property: value
UpDue to a parsing error, Internet Explorer 6 and below wouldn’t fail on properties that were prefixed with non-alphanumeric characters. Prefixing a regular property name with _
or -
will cause the property to be applied to Internet Explorer 6 and below but generally not in other browsers. Internet Explorer 7 had this bug fixed.
The CSS specification allows browsers to use an underscore (_
) or hyphen (-
) as a prefix for a vendor-specific property name with the guarantee that such properties will never be used in a future CSS standard. Because of this guarantee, these two prefix characters are ideal options for this hack.
Although the CSS specification defines this vendor-specific property syntax, the properties are inherently not part of any W3C-endorsed CSS profile and are therefore invalid when validated against one. For this reason, and because there is an often acceptable alternative, this hack is unrecommended.
_property: value
and -property: value
apply the property value in IE 6 and below. Warning: this uses invalid CSS.
*property: value
UpAlthough Internet Explorer 7 corrected its behavior when a property name is prefixed with an underscore or a hyphen, other non-alphanumeric character prefixes are treated as they were in IE6. Therefore, if you add a non-alphanumeric character such as an asterisk (*
) immediately before a property name, the property will be applied in IE and not in other browsers. Unlike with the hyphen and underscore method, the CSS specification makes no reservations for the asterisk as a prefix, so use of this hack could result in unexpected behavior as the CSS specifications evolve.
*property: value
applies the property value in IE 7 and below. It may or may not work in future versions. Warning: this uses invalid CSS.
body:empty
UpThe :empty
pseudo-classes is proposed for CSS 3 and should select an element that has no elements or text inside it. However, when used on the body
element, Firefox 1.5 and 2.0 (and corresponding versions of other Gecko-based browsers) always select it even when the body has content (which it should always have).
Although this hack is expected to be valid in CSS 3, it has not yet reached W3C Recommendation status and is invalid CSS 2.x, so it currently isn’t recommended to use this hack. However, it is probably the best way to single out recent versions of Firefox.
body:empty {}
selects the body
element in Firefox 1.5 and 2.0 only. It may or may not work in future versions. Warning: this uses invalid CSS 2.x but valid CSS 3 according to recent drafts.
a:link:visited, a:visited:link
UpAccording to the CSS standard, the :link
and :visited
link states are mutually exclusive: :link
actually means “unvisited link”. However, IE 7 and below will ignore one of these pseudo-classes if the other appears later in the same simple selector.
If you have the tag <a href="foo.html" id="linkhack">
, either #linkhack:link:visited {}
or #linkhack:visited:link {}
will select the element in IE 7 and below. The two selectors can be combined for a single declaration block: #linkhack:link:visited, #linkhack:visited:link {}
. In IE 7, you can also use an adjacent sibling combinator (+
) to select other elements near the link.
This uses perfectly valid CSS, but this method is less practical than some of the above methods and is therefore not recommended.
a:link:visited, a:visited:link {}
selects an a
element in IE 7 and below. It may or may not work in future versions.
>body
UpIf a simple selector is missing on either side of the child combinator (>
), Internet Explorer 7 incorrectly assumes that the missing simple selector is a universal selector. So >body
is treated by IE7 like *>body
, while other browsers ignore it because it’s a parsing error. Similarly, IE7 treats >>
like *>*>*
.
IE7 has the same quirk with other combinators. +p
is treated like *+p
and ~p
is treated like *~p
. (Note: The ~
combinator is an upcoming CSS 3 feature and is not valid CSS 2.1.)
>body {}
selects the body element in IE 7 only. It may or may not work in future versions. Warning: this uses invalid CSS!
html*
UpInternet Explorer 7 fixed the quirk that allowed the universal selector (*
) to select some nonexistent parent of the html
element, but there’s another issue that they didn’t fix: When a universal selector is directly adjacent to another simple selector without a space between, Internet Explorer 7 assumes a space there. That means that html*
is treated by IE7 like html *
, while other browsers ignore it because it’s a parsing error. Similarly, IE7 treats **
like * *
.
html* {}
selects all descendants of the html
element in IE 7 and below. It may or may not work in future versions. Warning: this uses invalid CSS!
!ie
UpInternet Explorer 7 fixed one of the issues with the !important
identifier, but it still has problems when the identifier has an error in it. If an illegal identifier name is used in place of important
, Internet Explorer 7 and below will handle the property normally instead of failing. Therefore, in any style declaration block, you can include properties intended to only apply to Internet Explorer and add an !ie
identifier. Almost any word can be used in place of ie
.
The !ie
identifier allows the property to be applied in IE 7 and below. It may or may not work in future versions. Warning: this uses invalid CSS!
!important!
UpAnother problem with the !important
identifier that wasn’t fixed in IE 7 is the treatment of non-alphanumeric characters after the identifier. Normally, this should cause the property to fail, but Internet Explorer 7 and below ignore the additional punctuate and apply the property as if it just had the !important
identifier.
The !important!
identifier allows the property to be applied with importance in IE 7 and below and the property is not applied in other browsers. It may or may not work in future versions. Warning: this uses invalid CSS!