CycleTracker: Base HTML and CSS
To build a PWA, a progressive web application, we need to create a fully-functioning web application. In this section, we will markup the HTML for a static web page and enhance the appearance with CSS.
Our project is to create CycleTracker, a menstrual cycle tracker. The first step in this introductory PWA tutorial is to write the HTML and CSS. The top section of the page is a form for the user to enter the start and end dates of each period. The bottom is a list of previous menstrual cycles.
We create an HTML file, with meta data in the head and a static web page containing a form and a placeholder to display user inputted data. We'll then add an external CSS stylesheet to improve the site's appearance.
To complete this tutorial, it is helpful to have a basic level of understanding of HTML, CSS, and JavaScript. If you're not familiar with these, MDN is the home of Getting Started, an introduction to web development series.
In the next sections, we'll set up a local development environment and take a look at our progress before adding JavaScript functionality to convert the static content created in this section into a functional web application. Once we have a functioning app we will have something that we can progressively enhance into a PWA that is installable and works offline.
Static web content
Our static site HTML, with placeholder <link>
and <script>
elements for yet-to-be-created external CSS and JavaScript files, is:
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Cycle Tracker</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>Period tracker</h1>
<form>
<fieldset>
<legend>Enter your period start and end date</legend>
<p>
<label for="start-date">Start date</label>
<input type="date" id="start-date" required />
</p>
<p>
<label for="end-date">End date</label>
<input type="date" id="end-date" required />
</p>
</fieldset>
<p>
<button type="submit">Add Period</button>
</p>
</form>
<section id="past-periods"></section>
<script src="app.js" defer></script>
</body>
</html>
Copy this HTML and save it in a file called index.html
.
HTML content
Even if the HTML in index.html
is familiar to you, we recommend reading through this section before adding some temporary hard-coded data, adding CSS to a style.css
external stylesheet, and creating app.js
, the application's JavaScript that makes this web page function.
The HTML's first line is a doctype preamble, which ensures the content behaves correctly.
<!doctype html>
The root <html>
tags wrap all the content with the lang
attribute defining the primary language of the page.
<!doctype html>
<html lang="en-US">
<!-- the <head> and <body> will go here -->
</html>
Document head
The <head>
contains machine-readable information about the web application that's not visible to readers except for the <title>
, which is displayed as the heading of the browser tab.
The <head>
includes all the metadata. The first two bits of information in your <head>
should always be the character set definition, which defines the character encoding, and the viewport <meta>
tag, which ensures the page renders at the width of the viewport and isn't shrunken down when loaded on very small screens.
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
</head>
We set the title of the page to "Cycle Tracker" with the <title>
element. While the contents of the <head>
are not displayed within the page, the contents of the <title>
are seen! The <title>
element's inner text appears in the browser tab when the page is loaded, in search engine results, and is the default title used when a user bookmarks a web page. The title also provides an accessible name for screen reader users who depend on it to know which tab they're currently on.
While the title could be "Menstrual cycle tracking application", we opted for a shortened name that is more discreet.
<title>Cycle Tracker</title>
While officially optional, for better user experience, these two <meta>
tags and the <title>
are the three components of the <head>
that should be considered required components of any HTML document.
For right now, the last component we include in the <head>
is a <link>
element linking style.css
, our yet-to-be-written stylesheet, to our HTML.
<link rel="stylesheet" href="style.css" />
The HTML <link>
element is used to specify a relationship between the current document and an external resource. There are more than 25 defined values for the rel
attribute—and many more values that are not in any specification. The most common value, rel="stylesheet"
, imports an external resource as a stylesheet.
We will revisit the <link>
element and its rel
attribute in a future section when we include the link to the manifest file.
Document body
The <body>
element contains all the content we want displayed when users visit the site on the Internet.
Within the <body>
, we include the name of the app as a level-1 heading using an <h1>
and a <form>
.
<body>
<h1>Period tracker</h1>
<form></form>
</body>
The form will contain instructions, form controls, a label for each form control, and a submit button. In terms of form controls, we need the user to enter both a start date and an end date for each menstrual cycle submitted.
Within the <form>
, we include a <fieldset>
with a <legend>
labeling the purpose of that set of form fields.
<form>
<fieldset>
<legend>Enter your period start and end date</legend>
</fieldset>
</form>
The date pickers are <input>
elements of type date. We include the required
attribute to reduce user errors by preventing the user from accidentally submitting an incomplete form.
To associate a <label>
with a form control, each <input>
has an id
attribute matching the for
attribute of the associated <label>
. The associated label provides each <input>
with an accessible name.
<label for="start-date">Start date</label>
<input type="date" id="start-date" required />
Putting it altogether, within the <fieldset>
, we include two paragraphs (<p>
elements), each with a date picker for the start and end dates of the menstrual cycle currently being entered, along with the date pickers' associated <label>
s. We also include a <button>
element which submits the form; we label it "Add period" by including that text between the opening and closing tags. The type="submit"
is optional, as submit
is the default type for <button>
.
<form>
<fieldset>
<legend>Enter your period start and end date</legend>
<p>
<label for="start-date">Start date</label>
<input type="date" id="start-date" required />
</p>
<p>
<label for="end-date">End date</label>
<input type="date" id="end-date" required />
</p>
</fieldset>
<p>
<button type="submit">Add Period</button>
</p>
</form>
We encourage you to learn more about making accessible web forms.
Temporary hard-coded results text
We then include an empty <section>
. This container will be populated using JavaScript.
<section id="past-periods"></section>
When the user submits the form, we will use JavaScript to capture the data and present a list of past periods along with a header for the section.
For the time being, we temporarily hardcode some content within this <section>
, including an <h2>
header and a few past periods, to have something to style as we write the page's CSS.
<section id="past-periods">
<h2>Past periods</h2>
<ul>
<li>From 01/01/2024 to 01/06/2024</li>
<li>From 01/29/2024 to 02/04/2024</li>
</ul>
</section>
This content, other than the container <section id="past-periods"></section>
, is temporary. We will remove or comment-out this temporary data once we complete the CSS and are satisfied with the app's appearance.
JavaScript link
Before closing the </body>
, we include a link to the yet-to-be-written app.js
JavaScript file. We include the defer
attribute to defer the loading of this script and ensure the JavaScript is executed after the document's HTML has been parsed.
<script src="app.js" defer></script>
The app.js
file will include all the workings of our application, including the event handlers for the <button>
, saving the data submitted to local storage, and displaying cycles within the content of the body.
The HTML file for this step is now complete! You can open the file in your browser at this point, but you'll notice that it's quite plain. We'll fix that in the next section.
CSS content
We can now style the static HTML using CSS. Our final CSS is:
body {
margin: 1vh 1vw;
background-color: #efe;
}
ul,
fieldset,
legend {
border: 1px solid;
background-color: #fff;
}
ul {
padding: 0;
font-family: monospace;
}
li,
legend {
list-style-type: none;
padding: 0.2em 0.5em;
background-color: #cfc;
}
li:nth-of-type(even) {
background-color: inherit;
}
If every line is familiar to you, you can copy the above CSS, or write your own CSS, and save the file as style.css
, then finish up the static HTML and CSS. If anything in the above CSS is new to you, keep reading for an explanation.
CSS explained
We use the background-color
property to set a light green (#efe
) background color on the body
. Then on the unordered list, fieldset, and legend, we use a white (#fff
) background color, along with a thin solid border added with the border
property. We override the background-color
for the legend, making the legend and the list items a darker green (#cfc
).
We use the :nth-of-type(even)
pseudo-class selector to set every even-numbered list item to inherit
the background color from its parent; in this case, inheriting the #fff
background color from the unordered list.
body {
background-color: #efe;
}
ul,
fieldset,
legend {
border: 1px solid;
background-color: #fff;
}
li,
legend {
background-color: #cfc;
}
li:nth-of-type(even) {
background-color: inherit;
}
To make the unordered list and list items not look like a list, we remove the padding by setting padding: 0
on the ul
and remove the list markers by setting list-style-type: none
on the list items themselves.
ul {
padding: 0;
}
li {
list-style-type: none;
}
We add a little white space by setting the body
's margin
using the vw
and vh
viewport units, making white space on the outside of our app be proportional to the viewport's size. We also add a little padding to the li
and legend
. Finally, to improve, but not fix, the alignment of the past-periods data, we set the font-family
of the ul
results section to be monospace
, making each glyph have the same fixed width.
body {
margin: 1vh 1vw;
}
ul {
font-family: monospace;
}
li,
legend {
padding: 0.2em 0.5em;
}
We can combine the above, putting multiple properties in each selector declaration block. We can even put the styles for the li
and legend
together; irrelevant styles, like the list-style-type
declaration on legend
, are ignored.
body {
margin: 1vh 1vw;
background-color: #efe;
}
ul,
fieldset,
legend {
border: 1px solid;
background-color: #fff;
}
ul {
padding: 0;
font-family: monospace;
}
li,
legend {
list-style-type: none;
padding: 0.2em 0.5em;
background-color: #cfc;
}
li:nth-of-type(even) {
background-color: inherit;
}
If any of the above CSS still looks unfamiliar to you, you can look up the CSS properties and selectors, or work through the CSS Styling basics module.
Whether you use the above CSS verbatim, edit the above styles to your preference, or write your own CSS from scratch, include all the CSS in a new file and save it as style.css
in the same directory as your index.html
file.
Finishing the static HTML and CSS for our PWA
Before moving on, comment out or delete the fake past period data and header:
<section id="past-periods">
<!--
<h2>Past periods</h2>
<ul>
<li>From 01/01/2024 to 01/06/2024</li>
<li>From 01/29/2024 to 02/04/2024</li>
</ul>
-->
</section>
Up next
Before adding the JavaScript functionality to convert this static content into a web app and then enhancing it into a progressive web app with a manifest file and service worker, we'll create a local development environment to view our progress.
Until then, you can view the static CycleTracker shell and download the CycleTracker HTML and CSS source code from GitHub.