CSS
===
CSS (Cascading Style Sheets) is the default method for styling HTML documents. CSS files are always linked into HTML documents using the :code:` ` element in the :code:`
`:
.. code-block:: html
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:
.. code-block:: css
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:
.. code-block:: css
h1 {
}
Id selector
The id selector allows you to apply a set of styles to the element with the given :code:`id` attribute value:
.. code-block:: html
Logout
.. code-block:: css
#logout {
}
Class selector
The class selector allows you to apply a set of styles to any element with the given :code:`class` attribute value:
.. code-block:: html
Right-aligned text
.. code-block:: css
.text-right {
}
.. important::
The :code:`class` attribute is actually interpreted not as a single value, but as a list of values separated by spaces. Thus
.. code-block:: html
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:
.. code-block:: css
a:hover {
}
or where the elements have a specific position:
.. code-block:: css
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:
.. code-block:: css
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:
.. code-block:: css
ul li {
}
In this example the rule will be applied to all :code:`` elements that are descendants of a :code:`` element. This would, for example, exclude any :code:`` that are in ordered :code:`` lists.
And
To combine multiple selectors so that are all tested against the same element, combine them without a space:
.. code-block:: css
ul[role=menu].horizontal {
}
This CSS rule will only apply to elements that are :code:`` elements, that have the attribute :code:`role` set to "menu", and that have "horizontal" in their :code:`class` attribute.
Child
The child combination ensures that the style only applies to an element that is the direct child of the parent element:
.. code-block:: css
li > a {
}
This CSS rule will only apply to :code:`` elements that are children of :code:`` 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
+++++++
:code:`display`
The :code:`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 :code:`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
++++++
:code:`flex-direction`
Set on the flex container and controls whether the flex items are displayed horizontally ("row") or vertically ("column").
:code:`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.
:code:`position`
The :code:`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 :code:`body` element, or the first ancestor element that has the :code:`position` 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.
.. code-block:: css
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.
:code:`top` / :code:`bottom`
For any of the :code:`position` settings, calculate the position either based the :code:`top` or :code:`bottom` edge of the element and its positioning parent. You should only specify one of the two.
:code:`left` / :code:`right`
For any of the :code:`position` settings, calculate the position either based the :code:`left` or :code:`right` edge of the element and its positioning parent. You should only specify one of the two.
:code:`width`
The width of the element.
:code:`height`
The height of the element.
Transforms
++++++++++
Transforms
:code:`transform`
The :code:`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:
.. code-block:: css
.popup {
display: fixed;
top: 50%;
left: 50;
transform: translate(-50%,-50%);
}
First the element uses fixed positioning together with setting the :code:`left` and :code:`top` 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 use :code:`transform` to move (translate) the element up and left by half its size (-50%). This works, because :code:`left` and :code:`top` are calculated based on the size of the parent element, but in :code:`transform` the percentages are calculated based on the size of the element itself.
Padding, Borders, Margins
+++++++++++++++++++++++++
:code:`padding`
The :code:`padding` specifies the amount of space between the content of the element and its border.
:code:`border`
The :code:`border` specifies the border style. It consists of three values "width", "style", "colour" separated by spaces.
:code:`margin`
The :code:`margin` specifies the margin between the border and the elements surrounding the current element.
:code:`box-sizing`
By default :code:`padding`, :code:`border`, and :code:`margin` 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 set :code:`box-sizing` to "border-box". In this mode, any :code:`padding` or :code:`border` changes not the :code:`width` or :code:`height` of the element, but rather the space available to the element's content.
Styling
+++++++
:code:`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.
:code:`font-size`
The size of the font.
:code:`font-weight`
The weight of the font. Common values are "normal", "bold", and "light".
:code:`font-style`
The style of the font. Common values are "normal" and "italic".
:code:`color`
The foreground colour to use for the element.
:code:`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.
:code:`px`
A specific pixel value. Should generally be avoided, as it struggles with devices with different pixel densities (a common scenario on modern smartphones).
:code:`%`
A percentage relative to another value. For :code:`width`, :code:`height`, :code:`left`, :code:`right`, :code:`top`, :code:`bottom`, :code:`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.
:code:`em`
A measure relative to the width of the letter "m". For the :code:`font-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's :code:`font` and :code:`font-size`.
:code:`rem`
A measure relative to the width of the letter "m" in the :code:`font` and :code:`font-size` set on the :code:`` element.
:code:`rgb-colour`
Colour value specified via its three components :code:`rgb(red, green, blue)`. Each of the colour values is between 0 and 255.
:code:`hex-colour`
Colour value specified via its three components in hex :code:`RRGGBB`. 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:
.. code-block:: css
@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:
.. code-block:: css
@media print {
}
or if the screen has a certain minimum width:
.. code-block:: css
@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 :code:` ` element. All that needs to be done is to create the ``style.css`` file and add the following content:
.. code-block:: css
/*
* 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:
.. code-block:: html
We can now look at the CSS to see how these styles are defined:
.. code-block:: css
.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 :code:`.grid-container-x`, which sets the maximum width of the element to 1200py and the :code:`box-sizing` to "border-box". Next the :code:`.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 :code:`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 :code:`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 :code:`` 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 :code:`display` flex.
.. code-block:: css
.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 :code:`.grid-x` rule configures the flex container. Because we want the grid to run in the x direction, the :code:`flex-direction` is set to "row". Next, in the :code:`.cell` rule we configure the basics of each flex item. In particular we specify :code:`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 :code:`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 :code:`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.
.. code-block:: css
.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 :code:`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 :code:`.small-hidden` rule, this time setting :code:`display` to block.
We can then use this class, for example, in the ``index.html`` in the :code:`` for the user navigation:
.. code-block:: html
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 :code:`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:
.. code-block:: css
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 :code:`` that has the :code:`role` "menu" is to be displayed using flex mode in a vertical column (via :code:`flex-direction`). Similarly any :code:`` with the :code:`role` "separator" that is inside a menu is given some extra spacing to separate two elements.