Tui components contain HTML that is structured semantically, using the HTML5 standard and provides containing wrappers enough to allow for a wide range of theming needs. Much of our HTML now, or in the future, needs to serve more than one purpose on a given web page once it is rendered. Because of this, we manipulate HTML by embedding logic within the markup, to control application of CSS selectors, ARIA attributes, conditional component trees and more.
We follow the principle of "material honesty": we use most appropriate element for the job, and only use a
span if no other element fits. If it's for navigation (it goes somewhere), we use a link. If it's for an action (it does something), we use a button. We use
checkboxes for multiple choice options. We use HTML5 input types (email, number, password, search, tel, url). Grids use
divs by default (since they are mostly used as containers for presentation), but can be set to use other more meaningful tags (such as
li) if appropriate.
We add ARIA roles where necessary: this is mostly just for custom components that don't have a native HTML element.
Here's a link being used as navigation.
<a href="/server/totara/competency/rate_competencies.php?user_id=1">Rate competencies</a>
Here's a button being used for action, with
type="button" so that it doesn't submit the form it's inside of.
<button type="button">Show filters</button>
For form controls, our framework renders a
label with a
for attribute, programmatically linking it to the relevant
input. For required fields, we show an asterisk, but hide it for screen reader users, replacing it with meaningful text (classes and other attributes omitted for clarity).
<label for="uid-1">Title <span title="Required"><span aria-hidden="true">*</span> <span class="sr-only">Required</span></span> </label> <input id="uid-1" name="title" type="text">
For groups of controls, we use
role="radiogroup" to group the controls and
aria-labelledby to programmatically link the group to its visible name appearing earlier in the DOM.
<label id="uid-1-label">Favourite colour</label> <div role="radiogroup" aria-labelledby="uid-1-label"> <input id="uid-1" type="radio" name="favouritecolor" value="red"> <label for="uid-1">Red</label> <input id="uid-1" type="radio" name="favouritecolor" value="green"> <label for="uid-1">Green</label> <input id="uid-1" type="radio" name="favouritecolor" value="blue"> <label for="uid-1">Blue</label> </div>
Tips and known limitations
- One limitation of Vue 2 is that components must have a single root element in their template. This element can change (e.g. with a
v-if) but there must only be one at once - this is enforced both by the linter and runtime warnings.
- If you need to have more than one root element, a workaround is sometimes possible - if your component doesn't have any state, you can define it as a functional component, which can have multiple root elements.
- Avoid writing invalid HTML as this will cause issues with server-side rendering - for example, if you put a
<div>inside of a
<div>will be moved outside the
<p>by the browser, meaning it will no longer match and cause will cause hydration errors on page load.
- The Mozilla Developer Network has an excellent, well ordered and well explained guide: HTML elements reference.
- For just the newer elements HTML5 Doctor's Element Index is a short and sweet guide.
- MarkSheet has a short, friendly, summary: HTML is about meaning.
- The W3C's Notes on ARIA Use in HTML give some good tips on writing semantic HTML.