CSS Hacks
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 mark-up between comments or to include part of a comment as if it was regular mark-up. 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 mark-up only to web browsers that match the condition (meaning only the selected versions of Internet Explorer). A negative conditional comment will expose the mark-up 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:
IE- Any version of IE
lt IE version- Versions less than version
lte IE version- Versions less than or equal to version
IE version- Only version version
gte IE version- Versions greater than or equal to version
gt IE version- Versions greater than 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: MSMSDN: 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 {} - ReRecent 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 targeted.
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">
&l<html lang="en">
<head>
<title>Test</title>
</head>
<body>
<input type="hidden" disabled id="attrhack">
<p>Test</p> </body>
</html>
For the above mark-up, here are the
selectors various browsers recognize to
select the p
element:
#attrhack[disabled=""]+p {}- Firefox 1.5 and below, possibly future versions
- Safari 2.0 and below, possibly future versions
- Konqueror 3.5 and below, possibly future versions
#attrhack[disabled="true"]+p {}- Opera 9 and below, possibly future versions
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"> &l<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> &l</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 behaviour 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"> &l<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 not recommended 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 not recommended.
_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 behaviour 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 behaviour 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!
This work is copyright © 2009 David Hammond and is licensed under a Creative Commons Attribution Share-Alike License. It may be copied, modified, and distributed freely as long as it attributes the original author and maintains the original license. See the license for details.