Yet another advantage of using a list-based structure is that it's easy to make precise changes to the menu styling, beyond what's available in the configuration file, simply by addressing the list elements from another stylesheet. This section explores some of the possibilities.
You can add these attributes to any of the elements in the tree. As we go through this chapter we'll look at various elements, and discuss what kind of styling you might want to do with them. Here I'm simply introducing the attributes with a view to which is most suitable in a given case:
Each link's visual styles are applied directly to anchors - the whole box is the link. The list-items are largely unstyled, apart from some 'invisible' properties such as margins between items. In practise this means that most of the external styling you do will be applied to anchors.
There are, however, some situations where you might want to style the list-items as well, to specific effect such as creating menu dividers. We'll talk more about that later, but first lets look at styling the navbar links.
You first need
an id
on each of the links you want to style.
So start with something like this:
<ul id="udm" class="udm">
<li><a id="homeButton" href="/">Home</a>
</li>
<li><a id="aboutButton" href="/menu/">About</a>
</li>
<li><a id="contactButton" href="/contact/">Contact</a>
</li>
</ul>
#homeButton {
...
}
Applying styles to each of our links is simple enough, but the script handles three different states - unvisited, visited and rollover, in that order. Here's how each state can be addressed:
id
-selector alone,
as we've just seen. Omitting the :link
pseudo-class means that
styles defined here can apply to all states,
unless they're otherwise defined:
#homeButton {
... default css ...
}
:visited
pseudo-class.
So:
#homeButton:visited {
... visited css ...
}
This is the most complex to address - we need
:hover
and :focus
first of all,
but also the visited state of :hover
(for cross-browser stability);
then we need to add the default and visited state
of the script's internal rollover
class, which is called "udmR"
. (The rollover class
is used to maintain persistence - so that when you
move to a menu its parent link remains highlighted,
which wouldn't happen with pseudo-classes alone).
Finally we'll add :active
, not actually
for a different active state, but to simulate
:focus
in
Win/IE
(it doesn't support :focus
but it does, incorrectly,
apply :active
to links
when they have the focus).
So:
#homeButton:hover, #homeButton:focus, #homeButton:visited:hover, #homeButton.udmR, #homeButton.udmR:visited, #homeButton:active {
... rollover css ...
}
If you find that any styles you're applying don't seem to have any effect, it's
most likely insufficient
specificity - ensure that these
rules come after the menu stylesheet, or
if that isn't enough, use
!important. You can also use !important
if you
want to force all states of a link to have
the same property, to save defining multiple rules:
#homeButton {
property:value !important;
}
The links in a horizontal navbar normally have "auto"
width,
but if you're making a pixel-precise layout then you might want
to set them exactly.
It should be as simple as applying width
to each
link in the navbar, but in
Opera 7.0 to 7.2 applying a fixed width to floated and positioned
elements is problematic - the navbar might collapse,
such that the links are all on top of each other. To avoid that
we have to apply widths to the list-items
as well as to the links; we can't apply them
just to list-items, or it won't work
in Opera 5. Phew!
So, we need an id
on
each list-item and link in the
navbar:
<ul id="udm" class="udm">
<li id="home"><a id="homeButton" href="/">Home</a>
</li>
<li id="about"><a id="aboutButton" href="/menu/">About</a>
</li>
<li id="contact"><a id="contactButton" href="/contact/">Contact</a>
</li>
</ul>
#home, #homeButton, #homeButton:visited {
width:50px;
}
#about, #aboutButton, #aboutButton:visited {
width:70px;
}
#contact, #contactButton, #contactButton:visited {
width:90px;
}
One way to deal with that is have IE6+ in Standards-compliant (sic) mode, and use the simplified box model hack to feed different widths to IE5:
#home {
width:74px;
}
#homeButton, #homeButton:visited {
width:74px;
w\idth:50px;
}
#about {
width:94px;
}
#aboutButton, #aboutButton:visited {
width:94px;
w\idth:70px;
}
#contact {
width:114px;
}
#contactButton, #contactButton:visited {
width:114px;
w\idth:90px;
}
#home {
width:74px;
}
#homeButton, #homeButton:visited {
width:50px;
}
@media screen,projection {
* html #homeButton, * html #homeButton:visited {
width:74px;
}
}
#about {
width:94px;
}
#aboutButton, #aboutButton:visited {
width:70px;
}
@media screen,projection {
* html #aboutButton, * html #aboutButton:visited {
width:94px;
}
}
#contact {
width:114px;
}
#contactButton, #contactButton:visited {
width:90px;
}
@media screen,projection {
* html #contactButton, * html #contactButton:visited {
width:114px;
}
}
Now you've seen the basic techniques for styling the navbar externally and then applying individual item widths, let's move onto a more detailed example, which uses these techniques to make an entirely image-based navbar.
For this we need not just id
-selectors, but also
a generic class
-selector.
We also need an extra <span>
inside each of the
links, which will be used to do
image-replacement - the <span>
should come
before the link text, or it won't work correctly
in Mac/IE5.
So we begin with a structure like this:
<ul id="udm" class="udm">
<li id="home"><a id="homeButton" class="navButton"
href="/"><span></span>Home</a>
</li>
<li id="about"><a id="aboutButton" class="navButton"
href="/menu/"><span></span>About</a>
</li>
<li id="contact"><a id="contactButton" class="navButton"
href="/contact/"><span></span>Contact</a>
</li>
</ul>
This does mean that menu indicators won't be present when images are turned off, but you can make up for that by adding some indicator-text directly into the relevant links:
<li id="about"><a id="aboutButton" class="navButton"
href="/menu/"><span></span>About ..</a>
</li>
Next we need some images, and we can put
both the default and rollover
states in the same actual image, then
use background-position
to change between them -
I got this idea from
Petr Stanicek [aka -pixy-].
Here's a sample image:
The great advantage of this is that no pre-loading is required - the rollover image will be instantly there.
The one disadvantage is it exposes the "image-flickering" bug in IE6, but there are a number of possible solutions to this, discussed in the article: Flickering images in Internet Explorer 6
Next we define the visible image size, as
dimensions of the navbar links. To avoid
box-model differences first set the
padding, margin and border-size of navbar links to "0"
from the
navbar item styles array, and make sure the
border-collapse is set to "separate"
.
If you're making a vertical navbar then the images will probably be all the same size:
/* link dimensions correspond with image dimensions */
.navButton, .navButton:visited {
width:126px;
height:36px;
position:relative !important;
}
If you're making a horizontal navbar then the images will probably be different widths, so you'll need to define them individually. We've already seen how to apply individual item widths, and this is the same as the simplest example:
/* link heights corresponds with image height */
.navButton, .navButton:visited {
height:36px;
position:relative !important;
}
/* link and list-item widths correspond with image widths */
#home, #homeButton, #homeButton:visited {
width:69px;
}
#about, #aboutButton, #aboutButton:visited {
width:75px;
}
#contact, #contactButton, #contactButton:visited {
width:92px;
}
/* span is same dimensions as link and positioned to superimpose */
.navButton span, .navButton:visited span {
display:block;
height:36px;
width:100%;
position:absolute;
left:0;
top:0;
z-index:1;
background-repeat:no-repeat;
}
You must not attempt to hide the underlying text, because you'll be hiding it from screenreaders as well, and also from browsers with CSS turned on but images turned off.
Each image is a different <span>
background,
so let's define those next:
/* button images are span background images */
#homeButton span, #homeButton:visited span {
background-image:url(/udm-resources/button-home.gif);
}
#aboutButton span, #aboutButton:visited span {
background-image:url(/udm-resources/button-about.gif);
}
#contactButton span, #contactButton:visited span {
background-image:url(/udm-resources/button-contact.gif);
}
Finally it just remains to implement the rollovers,
which is done with background-position
.
We've seen what rules are necessary to address
the rollover state, and this is just the same:
/* rollovers are background position so that no preloading is necessary */
.navButton span, .navButton:visited span {
background-position:0 0;
}
.navButton:hover span, .navButton:focus span, .navButton:visited:hover span, .navButton.udmR span, .navButton.udmR:visited span, .navButton:active span {
background-position:0 -100px;
}
The technique I've used here is one improvement on conventional FIR, in that it works when CSS is on but images are off. And since none of the text is 'hidden' or non-displayed in any way, screenreaders won't have a problem reading it.
However it does require an extraneous
<span>
inside the link, and also has its own potential
accessibility issue - the link dimensions are fixed in pixels,
so if the text is scaled to a very large size in a browser
with CSS on but
images off, it may begin
to disappear behind the border.
If that turns you off you may be interested in GIR - an alternative technique that doesn't have these issues, although it does rely on javascript and doesn't work fully in Mac/IE5.
For a discussion of image-replacement techniques in general, check out Using background-image to replace text. For some beautiful examples of image-replacement in action I recommend the CSS Zen Garden.
Although the styling available through the main configuration file is already comprehensive, there may be times when it's too comprehensive - if you just want to make small adjustments, it might seem excessive to define a whole new menu class or menu-item class.
So we can address the menus from external CSS, just as we addressed the navbar. Here are some ideas:
Many of the individual menus on this site
have different widths.
I've done that with <ul>
style
attributes:
<ul style="width:8em">
The main menus on this site have icons next to some of the links.
This is acheived by creating a space with padding-left, then
adding a non-tiled background-image
from external
CSS.
So first the padding, and we can use the menu-item additional CSS to set that from the configuration file:
'padding-left:29px;', // additional link CSS (careful!)
'padding-left:29px;', // additional hover/focus CSS (careful!)
'', // additional visited CSS (careful!)
<a href="/menu/benefits/" class="icon thumbsup">
The Benefits of UDM 4</a>
<a href="/menu/modules/" class="icon cog">
Modules & Extensions</a>
#udm a.icon {
background-repeat:no-repeat;
background-position:2px 2px;
}
#udm a.thumbsup {
background-image:url(/images/thumbsup.gif) !important;
}
#udm a.cog {
background-image:url(/images/cog.gif) !important;
}
#udm a.thumbsup {
background-image:url(/images/thumbsup.gif);
}
#udm a.thumbsup:hover, #udm a.thumbsup:focus, #udm a.thumbsup:active {
background-image:url(/images/thumbsup-rollover.gif);
}
#udm a.cog {
background-image:url(/images/cog.gif);
}
#udm a.cog:hover, #udm a.cog:focus, #udm a.cog:active {
background-image:url(/images/cog-rollover.gif);
}
//cache menu icons
var icons = [
'thumbsup',
'thumbsup-rollover',
'cog',
'cog-rollover'
];
var iconsLen = icons.length;
var imgs = [];
for(var i=0; i < iconsLen; i++)
{
imgs[i] = new Image;
imgs[i].src = '/images/' + icons[i] + '.gif';
}
Since the links are styled while the
list-items are plain, we can use list-item
margin
to create spaces between adjacent links.
Or we could use a combination of
margin
and padding
to place a border
halfway between
two links, and that's what I'm going to show you here.
You can see it in use with
the menus on this site -
many of them have dividers like these:
/* menu dividers */
.udm li.dividerBelow {
margin-bottom:2px !important;
padding-bottom:2px;
border-bottom:2px solid #ecefc6;
}
.udm li.dividerAbove {
margin-top:2px !important;
padding-top:2px;
border-top:2px solid #ecefc6;
}
<li class="dividerBelow"><a href="/products/">Products</a></li>
<li><a href="/services/">Services</a>
<ul>
...
</ul>
</li>
<li><a href="/products/">Products</a>
<ul>
...
</ul>
</li>
<li class="dividerAbove"><a href="/services/">Services</a></li>
In addition to
global transition effects (for IE5.5 or IE6),
you can add individual menu transitions in the same was
as we added
menu widths - using a style
attribute, or a
class
or id
plus external
CSS.
For example, class
names like these:
<ul class="fade">
<ul class="pixelate">
<ul class="dissolve">
ul.fade {
filter:progid:DXImageTransform.Microsoft.Fade(duration=0.3);
}
ul.pixelate {
filter:progid:DXImageTransform.Microsoft.Pixelate(duration=0.5);
}
ul.dissolve {
filter:progid:DXImageTransform.Microsoft.RandomDissolve(duration=1);
}
They can be quite annoying, and might also be very slow for people on older machines or with low amounts of RAM.
As an alternative to proprietary filters, you can have cross-browser sliding transition effects using the sliding menus extension, with similar reservations for people using older machines.
Until recently, I considered
Pure CSS menus to be
unimplementable - because li:hover
triggers interfere
with the
menu open and close timers, but there seemed no way
to apply those triggers only when scripting is unavailable,
because <noscript>
in the <head>
is not allowed.
Pure CSS menus have extremely poor usability, because of the lack of event-discriminated timers; but for browsers that support them and have scripting enabled, that would be as good as the menus ever get - for everyone, all the time.
Then suddenly I realised, there is a safe way to implement pure CSS menu triggers after all. It's really obvious actually - we just have to get the script to remove them.
Now, removing rules from an arbitrary style sheet requires DOM2 CSS, which is only properly supported in mozilla browsers. So what we have to do is to put the rules in a separate stylesheet, which the script can identify and remove.
I will re-iterate that I still consider pure CSS menus to have poor usability. Their invention was a major innovation, but as with many such things - although it opens our minds to a new way of doing something, the prototype itself is functionally useless in the real world. I think it's better, overall, if dropdown or flyout menus don't work without javascript.
Anyway that's my opinion. If you want them, here's what you do:
You must be using Version 4.4 or later to do this. If you're using an earlier version, please download the latest update first.
Please note, first of all, that implementing pure CSS menus adds some very specific limitations to your menu layout and design. Failing to account of any one of these could make your menus completely unuseable:
Note also that there won't be any submenu-indicator arrows, because these are added dynamically.
The navbar and menu design still takes place in your
udm-custom
file as before; but the CSS
required to open and position the menus without scripting must be
in a style sheet of it's own, with the id
"udm-purecss"
:
<!-- pure CSS menu triggers -->
<link rel="stylesheet" type="text/css" media="screen, projection"
id="udm-purecss" href="/udm-resources/udm-purecss.css" />
/* menu triggers */
ul[id="udm"] li > ul { visibility:visible; display:block; left:-10000px; overflow:visible !important; }
ul[id="udm"] li:hover > ul { left:auto; height:auto; }
/* submenu offset */
#udm li ul {
margin:-2.65em 0 0 6.5em;
}
/* child-menu offset */
#udm li ul ul {
margin:-2.2em 0 0 8.7em;
}
You can download a basic style sheet for each orientation using the links below:
They'll work in the following browser builds: Opera 7.5+, Mozilla 1.1+, Safari 1.2+, Konqueror 3.2+, Internet Explorer 7+.
The script-generated stylesheet outputs only to
"screen"
and "projection"
media.
This means that, by default,
the navigation list will be printed but not print-styled.
It's not practical to make the navbar and menus appear the same to print as they do to screen - it would take more code than it's worth, and since navigational elements are of little or no value to the printed page anyway, I'd recommend simply hiding the navigation, so it doesn't print at all.
You can do this using print media
CSS in a separate
style sheet or <style>
block:
<style type="text/css" media="print">
/* hide the navbar */
.udm { display:none; }
</style>
Practical implementations of aural CSS are rare - in fact there is only one graphical browser that supports it, namely Opera (Version 8 or later).
My knowledge of the subject is in its infancy; all I've done so far is add gender to different elements - the content is male and the navigation female:
html, body {
voice-family:male;
}
#udm {
voice-family:female;
}
The style sheet is included using a <link>
element
which has no media
attribute,
because Opera considers
aural CSS part of "screen"
rather than "aural"
media. This may change
in the future, so omitting the media
attribute (or setting it to "all"
) is the safest thing to do.
I'll write more as I learn more. For now, I refer you to some useful resources on the subject:
UDM 4 is valid XHTML, and in our judgement, meets the criteria for WAI Triple-A conformance.