CSS¶
CSS (Cascading Style Sheets) is the default method for styling HTML documents. CSS files are always linked into HTML documents using the <link>
element in the <head>
:
<link rel="stylesheet" href="path/to/style.css"/>
You can provide CSS properties in the HTML code directly, but fundamentally you should never do this, as it makes the code much harder to maintain.
When writing stylesheets, the core principle to keep in mind is that complex designs can be achieved by combining selectors, having multiple rules that match the same element with different properties, and to use media queries to adapt to screen sizes.
Basic Syntax¶
At the most basic level, a CSS stylesheet is an ordered list of CSS rules. Each CSS rule defines a selector that specifies which elements are styled by the rule and then a set of properties that are used for the styling. A single CSS rule looks like this:
selector {
property-name: property-value;
property-name: property-value;
}
Selectors¶
Tag selector
The tag selector allows you to apply the same set of styles to all elements with the given tag:
h1 { }
Id selector
The id selector allows you to apply a set of styles to the element with the given
id
attribute value:<a id="logout">Logout</a>#logout { }
Class selector
The class selector allows you to apply a set of styles to any element with the given
class
attribute value:<div class="text-right">Right-aligned text</div>.text-right { }Important
The
class
attribute is actually interpreted not as a single value, but as a list of values separated by spaces. Thus<nav class="cell shrink small-hidden"></nav>has three classes set on it “cell”, “shrink”, and “small-hidden”.
Pseudo selector
The pseudo selector actually represents a category of selectors, that allow you to select an element based on a computed property of some kind. Examples are selectors that apply only when the user hovers the mouse over the element:
a:hover { }or where the elements have a specific position:
tr:nth-child(2n) { }
Attribute selector
The attribute selector allows you to apply a set of styles to any element that has a given attribute with a given value:
ul[role=menu] { }
Full documentation on the available selectors can be found here: https://developer.mozilla.org/en/docs/Web/CSS/CSS_Selectors.
Combining Selectors¶
Often you will want to combine multiple selectors to create CSS rules that only apply to a very specific set of elements. To achieve this there are multiple combination operators
Descendant
By simply separating multiple separators using spaces, they are combined via the descendant rule:
ul li { }In this example the rule will be applied to all
<li>
elements that are descendants of a<ul>
element. This would, for example, exclude any<li>
that are in ordered<ol>
lists.
And
To combine multiple selectors so that are all tested against the same element, combine them without a space:
ul[role=menu].horizontal { }This CSS rule will only apply to elements that are
<ul>
elements, that have the attributerole
set to “menu”, and that have “horizontal” in theirclass
attribute.
Child
The child combination ensures that the style only applies to an element that is the direct child of the parent element:
li > a { }This CSS rule will only apply to
<a>
elements that are children of<li>
elements, but, for example, not anywhere else in the document.
Properties¶
CSS has a very large amount of properties, so the list here represents a small sub-set of the most frequently used properties.
Display¶
display
The
display
property controls how the matching elements are displayed. Common values are “none” (to hide an element), “block” (to display as a block element), “inline” (to display as an inline element), “inline-block” (to display as an inline element, but with block properties such as padding and margin), “flex” (to display as a block flex container).“flex” is a special value that controls not just how the element is displayed, but also how its children are positioned. By setting
display
to “flex” the element becomes a flex container and its children flex items. In this mode, the children are displayed in a horizontal or vertical layout with the width (or height) of the elements evenly distributed. This enables the creation of complex grid layout structures
Layout¶
flex-direction
Set on the flex container and controls whether the flex items are displayed horizontally (“row”) or vertically (“column”).
flex
Set on the flex item and controls how the width of the element adapts based on the width of the flex container. The value consists of three sub-values split by spaces (“grow shrink base”), but only the first two should always be specified. The grow and shrink values are relative between the flex items. Thus if all flex items have the same grow values, when extra space becomes available, the space is evenly split between all flex items. If one flex item has the grow set to 1 and the other to 0, then all extra space is allocated to the first element. The base value allows you to specify the initial size of the element. If not specified, the initial value is calculated from the element’s content.
position
The
position
property allows you to explicitly position an element either “relative” to its default position, “absolute” calculated from the positioning parent, “fixed” calculated from the browser viewport (the area of the screen that the browser occupies), “sticky” when scrolling.When using “relative”, the element is simply moved, but the space it used to occupy is not allocated to other elements.
When using “absolute”, the element is positioned absolutely and the space it used to occupy is allocated to its next sibling element. The element that is used to calculate the absolute position from is either the
body
element, or the first ancestor element that has theposition
property set.When using “fixed”, the element is positione absolutely, but it is the browser viewport (the area of the browser in which you can see part of the HTML document) that is used to calculate the position.
When using “sticky”, the element is positioned normaly, until it would scroll out of view, then it sticks to the last visible position inside the scroll parent.
header { position: sticky; top: 0; }In the example above the element is positioned normally until you scroll so that it’s top position is less than 0 (relative to the viewport). Then the top position remains fixed at 0.
top
/ bottom
For any of theposition
settings, calculate the position either based thetop
orbottom
edge of the element and its positioning parent. You should only specify one of the two.
left
/ right
For any of theposition
settings, calculate the position either based theleft
orright
edge of the element and its positioning parent. You should only specify one of the two.
width
The width of the element.
height
The height of the element.
Transforms¶
Transforms
transform
The
transform
property allows you to transform an element. This is similar to relative positioning, but much more flexible. One of the most common use-cases is to position an element in the centre of the screen:.popup { display: fixed; top: 50%; left: 50; transform: translate(-50%,-50%); }First the element uses fixed positioning together with setting the
left
andtop
to “50%” (so the middle of the page) to ensure that the popup always appears in the centre of the browser. Because the element has a certain width and height, this would always lead to it being off-centre. We then usetransform
to move (translate) the element up and left by half its size (-50%). This works, becauseleft
andtop
are calculated based on the size of the parent element, but intransform
the percentages are calculated based on the size of the element itself.
Padding, Borders, Margins¶
padding
Thepadding
specifies the amount of space between the content of the element and its border.
border
Theborder
specifies the border style. It consists of three values “width”, “style”, “colour” separated by spaces.
margin
Themargin
specifies the margin between the border and the elements surrounding the current element.
box-sizing
By defaultpadding
,border
, andmargin
are simply added to the width of the element’s content. This is fine when we do not want to explicitly specify either width or height, but if we want the element to be, for example, exactly 10% wide and with a border, then because the border is added to the 10%, it is really difficult to make the element exactly 10% wide including the border. For this scenario we can setbox-sizing
to “border-box”. In this mode, anypadding
orborder
changes not thewidth
orheight
of the element, but rather the space available to the element’s content.
Styling¶
font-family
The name of the font to use for displaying text. Common default families are “serif”, “sans-serif”, and “monospace”, but any font name can be specified. However, it needs to be available on the device the browser is running on.
font-size
The size of the font.
font-weight
The weight of the font. Common values are “normal”, “bold”, and “light”.
font-style
The style of the font. Common values are “normal” and “italic”.
color
The foreground colour to use for the element.
background
The background colour to use for the element.
Units¶
Wherever a size or position value is required the following units can be used to specify that size or position.
px
A specific pixel value. Should generally be avoided, as it struggles with devices with different pixel densities (a common scenario on modern smartphones).
%
A percentage relative to another value. Forwidth
,height
,left
,right
,top
,bottom
,font-size
this is relative to the size of the parent element. For all others it is relative to the size of the current element.
em
A measure relative to the width of the letter “m”. For thefont-size
property this is calculated relative to the letter “m” in the parent element. For all other properties the size of the letter “m” in the current element’sfont
andfont-size
.
rem
A measure relative to the width of the letter “m” in thefont
andfont-size
set on the<body>
element.
rgb-colour
Colour value specified via its three componentsrgb(red, green, blue)
. Each of the colour values is between 0 and 255.
hex-colour
Colour value specified via its three components in hexRRGGBB
. Each of the colour values is between 0 and 255 in hexadecimal notation (00 - ff).
Media Queries¶
Media queries enable us to enable CSS rules when specific criteria are true:
@media rule {
}
All of the CSS rules specified within the curly brackets will only be taken into account by the browser if the rule is true.
Examples of rules are specific medium:
@media print {
}
or if the screen has a certain minimum width:
@media screen and (min-width 640px) {
}
Further details can be found here: https://developer.mozilla.org/en/docs/Web/CSS/Media_Queries.
Example¶
We will now add some styling to our two HTML documents. If you look at the HTML sourcecode, you will see that both files already load a stylesheet via the <link>
element. All that needs to be done is to create the style.css
file and add the following content:
/*
* Basic styles
*/
body {
margin: 0;
}
a:link {
color: rgb(73, 91, 115);
text-decoration: none;
}
a:visited {
color: #374557;
}
a:focus, a:hover {
color: rgb(92, 115, 145);
}
h1 {
margin-top: 0;
}
.text-right {
text-align: right;
}
/*
* Grid styles
*/
.grid-container-x {
max-width: 1200px;
margin: 0 auto;
box-sizing: border-box;
}
.grid-container-x.small {
max-width: 480px;
}
.grid-x {
display: flex;
flex-direction: row;
flex-wrap: wrap;
box-sizing: border-box;
}
.grid-x.grid-padding-x .cell {
padding: 0 1rem;
}
.cell {
display: block;
flex: 1 1;
box-sizing: border-box;
}
.cell.shrink {
flex: 0 0;
}
.cell.auto {
flex: auto;
}
.cell.small-12 {
flex: 0 0 100%;
}
@media (min-width: 640px) {
.cell.large-shrink {
flex: 0 0;
}
.cell.large-auto {
flex: auto;
}
}
/*
* Visibility classes
*/
.small-hidden {
display: none;
}
@media (min-width: 640px) {
.small-hidden {
display: block;
}
}
/*
* Menu styles
*/
ul[role=menu] {
display: flex;
flex-direction: column;
margin: 0;
padding: 0;
}
ul[role=menu].horizontal {
flex-direction: row;
margin: 0.5rem 0 0 0;
}
ul[role=menu] li {
list-style-type: none;
}
ul[role=menu].horizontal li {
margin-right: 0.5rem;
}
ul[role=menu] li[role=separator] {
padding-bottom: 0.5rem;
margin-bottom: 0.5rem;
}
ul[role=menu] li a {
display: block;
padding: 0.5rem 0.8rem;
white-space: nowrap;
}
ul[role=menu] li a.active {
color: rgb(255, 255, 255);
background: rgb(92, 115, 145);
}
/*
* Callout component
*/
.callout {
color: rgb(242, 242, 242);
background: rgb(73, 91, 115);
border: 1px solid rgb(55, 69, 87);
padding: 1rem;
}
/*
* Form styles
*/
label {
display: block;
width: 100%;
box-sizing: border-box;
margin-bottom: 0.5rem;
font-size: 90%;
font-variant: small-caps;
}
input {
display: block;
width: 100%;
box-sizing: border-box;
}
button {
cursor: pointer;
background: rgb(242, 242, 242);
border: 1px solid rgb(55, 69, 87);
padding: 0.3rem 0.8rem;
}
button:hover, button:focus {
color: rgb(242, 242, 242);
background: rgb(92, 115, 145);
}
/*
* Header styles
*/
header {
background: rgb(242, 242, 242);
border-bottom: 1px solid rgb(73, 91, 115);
margin-bottom: 2rem;
box-shadow: 0 0 5px rgb(196, 196, 196);
position: sticky;
top: 0;
z-index: 1;
}
header .app-title {
margin-top: 0.4rem;
padding: 0.5rem 0.8rem;
font-weight: bold;
white-space: nowrap;
}
/*
* Footer styles
*/
footer {
margin-top: 2rem;
border-top: 1px dotted rgb(73, 91, 115);
padding: 0.5rem;
font-size: 80%;
}
/*
* Table styles
*/
thead th {
text-align: left;
padding: 0 0.5rem 0.5rem 0.5rem;
}
thead th {
border-bottom: 1px solid rgb(73, 91, 115);
}
tbody td {
padding: 0.5rem;
}
tbody tr:nth-child(2n) td {
background: rgb(242, 242, 242);
}
tbody td {
padding: 0.5rem;
}
@media (max-width: 640px) {
table {
width: 100%;
}
table th {
display: none;
}
table td {
display: block;
}
}
/*
* Icon styles
*/
.mdi.warning:link, .mdi.warning:visited {
color: rgb(168, 38, 73);
}
.mdi.warning:hover, .mdi.warning:focus {
color: rgb(189, 43, 82);
}
If you now reload the page in the browser, you will see that the styling is much improved. Let us now have a look at some specific aspects, although you should spend some time looking at all the various bits of the CSS used.
Combining smaller CSS rules¶
If we look at the index.html
, the core content is defined as follows:
<main class="grid-container-x small callout">
We can now look at the CSS to see how these styles are defined:
.grid-container-x {
max-width: 1200px;
margin: 0 auto;
box-sizing: border-box;
}
.grid-container-x.small {
max-width: 480px;
}
.callout {
color: rgb(242, 242, 242);
background: rgb(73, 91, 115);
border: 1px solid rgb(55, 69, 87);
padding: 1rem;
}
The first CSS rule that matches is the .grid-container-x
, which sets the maximum width of the element to 1200py and the box-sizing
to “border-box”. Next the .grid-container-x.small
rule also matches, because the element has both those two classes. Because the selector has more parts (and is thus more specific), the properties set in this overwrite any properties set previously. That means that the max-width
is now reduced to 480px. The third rule again matches on a single CSS class and sets some font and background colour together with a border and some padding. The advantage of this combinatorial approach is that we can, for example, also use the callout
CSS class in another element to apply the styling, without applying the width contraints, making the whole structure very flexible.
You can check that this is how the browser is actually applying the CSS by navigating to the login.html
page, then right-clicking into the dark-blue box, and selecting “Inspect”. This will open the developer tools in the DOM inspector mode, where you can see the HTML transformed into the Document Object Model (DOM) tree that the browser uses for rendering. You will on the right also see which CSS rules are applied to the currently selected element. Make sure that you select the <main>
element in the Inspector and then you will see that exactly those three CSS rules are being used.
Try changing some of the various values to see the effect in the browser.
Grid Layouts with Flex¶
Now let us take a quick look at how to create more complex layouts using display
flex.
.grid-x {
display: flex;
flex-direction: row;
flex-wrap: wrap;
box-sizing: border-box;
}
.cell {
display: block;
flex: 1 1;
box-sizing: border-box;
}
.cell.shrink {
flex: 0 0;
}
.cell.auto {
flex: auto;
}
The .grid-x
rule configures the flex container. Because we want the grid to run in the x direction, the flex-direction
is set to “row”. Next, in the .cell
rule we configure the basics of each flex item. In particular we specify flex
as “1 1” to ensure that any extra space is evenly distributed amongst the cells. This gives us the basics for creating a horizontal grid of equal size cells.
The next two CSS rules allow us to create specific cells that either shrink to the size of their content or expand as wide as possible. By setting flex
to “0 0”, we tell the browser not to give any element with the CSS classes “cell” and “shrink” any of the extra space available. Similarly by setting flex
to “auto”, we enable the default functionality of adding available space to the element.
Adapting with Media Queries¶
The fashion nowadays is to create a single HTML file for all device sizes and then use media queries to adapt to the specifics of the device.
.small-hidden {
display: none;
}
@media (min-width: 640px) {
.small-hidden {
display: block;
}
}
The code above shows an example of showing and hiding content depending on the width of the screen. Here we first define the default action for any element that has the “small-hidden” CSS class, setting the display
to “none”, thereby hiding the element. Then we define a media query that is true if the width of the screen is greater than 640px. Inside the media query we redefine the .small-hidden
rule, this time setting display
to block.
We can then use this class, for example, in the index.html
in the <header>
for the user navigation:
<nav class="cell shrink small-hidden" aria-label="User">
If we open the page on a mobile device, the browser will hide the element because of the first CSS rule. The media query is not true, so nothing else will happen. If we open the page on the desktop, the browser will first hide the element due to the first rule. However, because the media query is true, the second rule will also be active. As it is specified after the first rule, it overwrites anything set in the previous rule, turning the display
back to “block” and showing the element.
We can test this by opening the developer tools (either from the browser menu, F12, or via the Inspect context menu). Now we need to simulate a smaller device. In Firefox this is called “Responsive Design Mode” in the top-right corner of the developer tools. In Chrome it is “Toggle device toolbar” in the top-left corner of the developer tools. This will make the browser simulate a smaller viewport. You will see that the user menu is now hidden. By resizing the viewport with your mouse, you can see when the media query becomes true and the element is displayed.
Using ARIA Roles for Styling¶
One of the elegant aspects of CSS is using the attribute selector to style elements based on their ARIA tags:
ul[role=menu] {
display: flex;
flex-direction: column;
margin: 0;
padding: 0;
}
ul[role=menu] li[role=separator] {
padding-bottom: 0.5rem;
margin-bottom: 0.5rem;
}
Here, for example, we specify that any <ul>
that has the role
“menu” is to be displayed using flex mode in a vertical column (via flex-direction
). Similarly any <li>
with the role
“separator” that is inside a menu is given some extra spacing to separate two elements.