Essay
CSS Curiosities of the Past
- 2601 words
The ebbs and flows of the web have gotten us to where we stand today. We simply wouldn’t have the web we do without the journey it has taken us to get here. However, some of the steps it has taken to get here have been interesting to say the least.
CSS is how we style things on the web, but since its inception it has been made, on occasion, to wear a few more hats. It has taken on odd roles and picked up behaviours it probably ought not to have. Such is life.
As I’ve detailed odd and context-specific HTML, this is a look at CSS largely outside the specifications. Browser-specific hacks, technology-scoped syntax, and engine-exclusive snippets forged from questionable circumstances, corporate complications, and esoteric implementations.
Property Parsing
width: 300px;
*width: 250px;
_width: 200px;
-width: 200px;
Most browsers, correctly, would treat a property prefixed with an asterisk as invalid. However, Internet Explorer 7 and earlier would treat it as valid. It was so famous it garnered the name ‘star hack’ for the shape of the asterisk. Likewise, when prefixing a property with an underscore or hyphen, only Internet Explorer 6 would treat it as valid. There were many more similar hacks used, the vast majority of which are best documented at the eponymous browserhacks.com. The main gist is that some browsers would incorrectly parse properties, selectors, and values, and that could be used for gain in an era where browser behaviour was rather varied.
Limiting CSS to apply only in certain browsers with conditional comments in CSS files, such as could be done in HTML documents, wasn’t possible.1 Therefore, this exploitation of questionable parsing of what is and isn’t valid was commonplace for targeting specific browsers.
Important
background: red !interesting;
Internet Explorer 7 and earlier would treat almost any textual string prefixed by an exclamation mark as !important. Most commonly, people would make use of this by writing !ie to have a style only override specificity in Internet Explorer. As far as Internet Explorer was concerned, the arbitrary !banana and the specced !important were the same, while other browsers correctly only accepted the latter.
There was another related bug in Internet Explorer 6 and lower where a style declared later in the same block would overwrite an !important value. For example, here the colour would be black rather than white, as it should be:
color: white !important;
color: black;
Document Level Browser Detection
There were so many ways in which you could identify what browser was in use on a document level via CSS. Some examples include:
- In Internet Explorer 6 and earlier you could write
* html {}. - For only Internet Explorer 7 you could use
*:first-child+html {}. - Browsers other than Internet Explorer 7 support
html > /**/ body {}. - Early versions of Firefox would apply styles within
body:empty {}(even when the body had contents).
This approach was often messy to use, as it meant writing multiple descending selectors to try to target elements in specific browsers only.
Clearfix
.clearfix {
zoom: 1;
}
Until version 8, Internet Explorer had a concept called hasLayout. An internal flag, it would designate whether an element was responsible for rendering itself (true) or if a parent element would be responsible for it (false). Naturally, this was an obtuse and confusing system. Some elements, such as <img>, <iframe>, and most things input-related, would intrinsically ‘have layout’. Other elements wouldn’t have layout, unless it was specifically given via specific CSS declarations being present.
An element without layout could encounter weird margin and border behaviours, positioning complications, and general odd rendering and, most importantly, would cause parent containers to collapse when their children were floated. This caused all sorts of problems in an age where floating elements was the de facto way to lay out pages.
The zoom property changes the size at which its target appears. With a value of 1, the target appears at its existing size. This may seem useless, but zoom gives an element layout, so it would be used as a simple way to do so without otherwise changing an element’s visual appearance.
Hand Cursor
cursor: pointer;
cursor: hand;
Prior to Internet Explorer 6, Internet Explorer didn’t respect the pointer value for the cursor property. Instead, it only supported the non-standard hand value, which provided a pointing hand. Therefore, sites would commonly write both values in their CSS with the expectation that cursor: hand would be considered invalid in browsers other than Internet Explorer, thus prompting them to use pointer while Internet Explorer uses hand.
CSS Expressions
top: expression(eval(document.documentElement.scrollTop));
Formally called ‘Dynamic Properties’, CSS expressions were a way to execute JavaScript in CSS. It was non-standard behaviour introduced in Internet Explorer 5 to patch up lagging styling capabilities, such as missing support for position: fixed (which the above snippet addresses), or min-width and max-width, among others. This made making layouts rather difficult. CSS Expressions were hacky and awkward while exposing a lot of complexity. You could go as far as to change what styles were applied based on the time of day using Date(). CSS expressions were constantly re-evaluated, making them extremely performance taxing. As was noted by Steve Souders on the Yahoo! Developer Network blog:
The problem with expressions is that they are evaluated more frequently than most people expect. Not only are they evaluated when the page is rendered and resized, but also when the page is scrolled and even when the user moves the mouse over the page.
Microsoft ended support for them with Internet Explorer 8, as announced in their dramatically titled post ‘Ending Expressions’.
Filters
Internet Explorer had a number of iffy behaviours and lagged behind other browsers for many visual effects, even regarding basic properties like opacity. Thus, Microsoft brought in bespoke filter functionality. Filters used DirectX-based components of Windows, far away from web platform standards. Internet Explorer first introduced its filter functionality with version 4. The syntax was rather simple, with an example of lowering an element’s opacity looking like this:
filter: alpha(opacity=50);
It was far from perfect but allowed addressing many of Explorer’s shortcomings. Filters required that an element had layout and also annoyingly stripped ClearType font anti-aliasing for small text, making it look jagged. Sometimes they’d cause issues with interactivity, too. In version 5.5 filter syntax became more complex, looking like this:
filter: progid:DXImageTransform.Microsoft.Alpha(opacity=50);
Note the presence of DX referring to DirectX. Filters could even be animated. In the last update to filter functionality, Microsoft vendor-prefixed their filter property in version 8 to better comply with CSS standards. They also made it so that filters would not strip font anti-aliasing, and it became necessary to wrap the value in quotes:
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(opacity=50)";
-ms-filter was finally removed with Internet Explorer 10. This removal extended to Internet Explorer’s legacy modes too. By version 10’s release, most popular uses for filters had received support from the browser, such as the opacity property which was added in version 9.
Aside from opacity, two of the other very common uses for Explorer’s filter capabilities were handling transparent PNG images and applying gradients. Transparent sections of PNGs in Internet Explorer 6 and earlier were replaced with a grey background. Even in later versions, PNGs could encounter issues usually related to gamut. Therefore, people would often load images via AlphaImageLoader:
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='image.png');
Gradients would be applied with filter as an alternative to using background images (linear-gradient() would not receive support until Internet Explorer version 10).
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='red', endColorstr='green', GradientType=0);
People would often implement simple reusable filters for commonly encountered situations using the aforementioned CSS Expressions functionality.
Scrollbars
body {
scrollbar-face-color: #333333;
scrollbar-highlight-color: #666666;
scrollbar-3dlight-color: #000000;
scrollbar-darkshadow-color: #000000;
scrollbar-shadow-color: #111111;
scrollbar-arrow-color: red;
scrollbar-track-color: #222222;
}
Internet Explorer version 5.5 introduced the ability to customise the appearance of scrollbars.
For the arrow buttons and thumb, face-color set background, highlight-color set the inner left highlight, 3dlight-color set the outer right highlight, darkshadow-color set the inner right shadow, shadow-color set the inner right shadow, and arrow-color set the arrow icons themselves. track-color obviously set the scrollbar track. There were even full applications for generating the styles.
Much like today, styling scrollbars was considered somewhat gaudy and over-the-top, so it was mostly used for minor tweaks in professional contexts. The functionality was of course utilised to its fullest potential by people customising their MySpace profiles.
HTML Components
.item {
behavior: url(csshover.htc);
}
HTML Components (not to be confused with contemporary Web Components) were an Internet Explorer exclusive feature added in version 5 and expanded greatly in version 5.5, which allowed attaching scripting logic to HTML elements via CSS. Though many languages were supported, JavaScript was almost always used.
Here is an example of a HTML Component that would be used to add hack-in hover behaviour on elements other than links, which is the only place :hover was supported in Internet Explorer prior to version 7. Note that the language specified is ‘JScript’ – Microsoft’s diverging implementation of JavaScript which Internet Explorer supported.
<PUBLIC:COMPONENT NAME="CSSHover">
<PUBLIC:ATTACH EVENT="onmouseover" ONEVENT="hoverOn()" />
<PUBLIC:ATTACH EVENT="onmouseout" ONEVENT="hoverOff()" />
<script language="JScript">
// Append a class when the mouse enters
function hoverOn() {
if (!/\bhovered\b/.test(element.className)) {
element.className += " hovered";
}
}
// Remove the class when the mouse leaves
function hoverOff() {
element.className = element.className.replace(/\b\s?hovered\b/g, "");
}
</script>
</PUBLIC:COMPONENT>
A particular popular usage of this functionality was Progressive Internet Explorer, which ported then-modern CSS features to Internet Explorer versions 6 through 9. From Internet Explorer 8, behaviour is available as the vendor prefixed -ms-behavior, and support was removed in Internet Explorer 10.
Box Model Hack
div {
width: 400px;
voice-family: "\"}\"";
voice-family: inherit;
width: 300px;
}
A rather famous hack from Tantek Çelik. The voice-family property was used for specifying which voice family should be used when reading content aloud. The property wasn’t supported by older browsers of the time, and the value was provided as a string. Due to a parser bug, Internet Explorer 5 would interpret "\"}\"" as the closing of the current CSS block, leaving the other declarations invalid and allowed to slip past the browser undetected. This was important, as implementations of the box model were differing.
The Windows releases of Internet Explorer caught by the hack – versions 5 and 5.5 – handled the box model incorrectly. They would include padding and borders inside the declared width, rather than outside as they should have. Meanwhile, standard-compliant browsers which parsed the CSS correctly (with the exception of Opera, for which a further snippet would be included) supported the box model and would correctly handle borders and margins as being outside of the element’s width.
Holly Hack
/* Hides from IE5 on Mac \*/
* html .element {
height: 1%;
}
/* End hide from IE5 on Mac */
Internet Explorer for Mac used an entirely different engine to Internet Explorer elsewhere. While most platforms used Trident/MSHTML, Explorer for Mac used Tasman, which had bugs of its own. One of these bugs was that comments with an escape character before closing (\*/) would be mishandled.
The content before the next properly closed comment would be treated as a comment by Internet Explorer for Mac and thus not parsed, but other browsers would handle it properly. In the above snippet a Holly Hack (named for Holly Bergevin) is paired with a hack browser detection targeting Internet Explorer 6 and earlier so that a fix to unwanted behaviour exclusive to Internet Explorer on Windows would only apply to Internet Explorer on Windows.
Double Margin Float
div {
float: left;
margin-left: 5px;
display: inline;
}
Internet Explorer would display the above margin as 5px. However, without a display value of inline, it’d be rendered as 10px – not 5px. As explained in Position Is Everything’s Explorer Exposed article, The IE Doubled Float-Margin Bug:
Why is this happening? Don’t ask such silly questions! This is IE, remember? Conformance with the specs is only to be hoped for, not expected. The simple fact is it does happen.
Toolbars
Before the Skype Toolbar opt-out meta tag was introduced – and even as a fallback after it was – some site owners would stop Skype’s Toolbar from displaying Skype buttons on their page directly with CSS:
span.skype_pnh_container {
display: none !important;
}
span.skype_pnh_print_container {
display: inline !important;
}
In a very similar case, the Ask Toolbar would appear at the top of the page and sometimes break pages, leading to developers targeting #apn-null-toolbar and #apn-body-style to remove it. Many other extensions, often installed by applications without users really understanding them, would introduce similar issues that browsers would have to target and remove.
Vendor Prefixes
Vendor prefixes were mainly used for non-standard or experimental CSS features so that developers could test new features and give feedback to browser vendors before their implementation or specification is finalised. ‘Prefix’ refers to the browser-specific mark preceding the property, pseudo, at-rule, or media feature. -webkit- was used for WebKit and Blink-based browsers, -moz- for Gecko-based browsers, -o- for Presto-based browsers, -khtml- for KHTML (and kept around for early WebKit versions), and -ms- for MSHTML/Trident-based browsers, as well as others.
However, these vendor prefixes would sometimes be prematurely adopted beyond experimenting. Developers would include them in production sites such that removing support for these testing features would break them. As such, browser vendors found themselves having to keep support for these testing implementations.
As a result, browser vendors no longer use vendor prefixes, instead placing features behind flags which must be enabled on each individual browser. Most vendor prefixes are obsolete in the face of non-prefixed implementations. However, some stick around as proper implementations haven’t been specced. Perhaps most famously, -webkit-text-stroke, which is supported across all major browsers and not just those using WebKit.
This article isn’t comprehensive. These are only the more popular or notable non-standard bits. Many more curious CSS oddities from time passed linger out there on the web, collecting dust and growing creaky. Even in 2005 Microsoft was trying to get people to stop using many of these hacks. At least (outside of legacy contexts) we don’t have to fret about the questionable behaviour of Internet Explorer and other stone-age browsers any longer.
Footnotes
-
People would sometimes get around this limitation by wrapping styles in conditional comments where they were imported, using conditional comments to selectively apply classes, or by wrapping inline styles within conditional comments, all within their HTML. ↩