Introduction
Although the documentation for Bootstrap is well done there are some areas that are not well documented. For instance, creating a modal window, search the web and note that most donβt address obtaining data back after the modal window is closed. Another example is with working with aria-checked, most code samples use pseudo elements which will not work with Bootstrap.
To address these and other techniques there are ten Microsoft Razor Pages projects to provide easy to follow code samples but with limited documentation as those studying the code samples should be fine.
Included projects
Each project name includes the version of Bootstrap and the topic so that when a new version of Bootstrap comes out they can be added with clear separation as usually new versions have breaking changes.
Visual Studio
Code samples are written using Visual Studio 2023, .NET Core 7 and will work if downgraded to .NET Core 6.
Source code
Clone the following GitHub repository
Checkboxes and aria-checked
When a developer decides to make their web application WCAG AA accessible there are additional considerations which in this project two topics are covered at a basic level.
First, using check boxes that allow a screen reader to know if a check box is checked or not which can only be done with setting the appropriate attributes and some JavaScript. Simply getting the checked property will not match aria-checked.
Setup for a checkbox
<div class="form-check">
<input class="form-check-input"
type="checkbox"
value=""
name="coffeeToppings"
id="addCreme"
aria-checked="True">
<label class="form-check-label" for="addCreme">Creme</label>
</div>
In the above example toggling the checked
state does not toggle aria-checked
. JavaScript is needed as per below.
// get all check boxes by bootstrap class
var checkboxes = document.getElementsByClassName("form-check-input");
// setup logic to toggle aria-checked
for(let index = 0;index < checkboxes.length;index++){
checkboxes[index].onclick = function(){
if (checkboxes[index].getAttribute('aria-checked') === 'true') {
checkboxes[index].setAttribute('aria-checked', 'false');
} else {
checkboxes[index].setAttribute('aria-checked', 'true');
}
};
}
Modal
Modals for experienced developers will be easy to implement but not for new developer as there are no really good working code samples I have seen.
Here we want to show a dialog centered on the screen. Go to Bootstrap documentation for modal and note the base code here is used.
Code behind, there are two string properties, Recipient and Message for storing data from the dialog and Accepted to double check the user's descision.
OnPost we assert there is data.
public class Dialog1Model : PageModel
{
[BindProperty]
public string Recipient { get; set; }
[BindProperty]
public string Message { get; set; }
[BindProperty]
public bool Accepted { get; set; }
public void OnPost()
{
if (Accepted)
{
if (!string.IsNullOrWhiteSpace(Recipient) && !string.IsNullOrWhiteSpace(Message))
{
Log.Information("Send to {P1} Message: {P2}",
Recipient,
Message);
}
else
{
Log.Information("Incomplete");
}
}
else
{
Log.Information("Cancelled");
}
}
}
Front end code
<div class="container">
<div class="row">
<button class="btn btn-primary w-25" id="btn-confirm">Confirm</button>
</div>
</div>
<form method="post" class="w-25">
<input type="hidden" asp-for="Recipient" id="recipientName" />
<input type="hidden" asp-for="Message" id="messageText" />
<input type="hidden" asp-for="Accepted" id="acceptedFlag" />
<input type="submit" value="Post" role="button" class="btn btn-primary mt-2 w-100"/>
</form>
<div class="modal fade"
id="mi-modal"
tabindex="-1"
aria-labelledby="exampleModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">New message</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close">
</button>
</div>
<div class="modal-body">
<form>
<div class="mb-3">
<label for="recipient-name" class="col-form-label">Recipient:</label>
<input type="text" class="form-control" id="recipient-name">
</div>
<div class="mb-3">
<label for="message-text" class="col-form-label">Message:</label>
<textarea class="form-control" id="message-text"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="modal-btn-no">Close</button>
<button type="button" class="btn btn-primary" id="modal-btn-yes">Send message</button>
</div>
</div>
</div>
</div>
@section Scripts
{
<script>
var modalConfirm = function (callback) {
$("#btn-confirm").on("click", function () {
$("#mi-modal").modal('show');
});
$("#modal-btn-yes").on("click", function () {
callback(true);
$("#mi-modal").modal('hide');
});
$("#modal-btn-no").on("click", function () {
callback(false);
$("#mi-modal").modal('hide');
});
};
modalConfirm(function (confirm) {
if (confirm) {
document.getElementById("recipientName").value = document.getElementById("recipient-name").value;
document.getElementById("messageText").value = document.getElementById("message-text").value;
document.getElementById("acceptedFlag").value = true;
} else {
document.getElementById("acceptedFlag").value = false;
}
});
</script>
}
- The three hidden elements are for storing data from the dialog
- modal-dialog-centered vertically centers the dialog, remove for the dialog to appear at the default location.
- Important mi-modal, id for the class wrapping the modal must match the code in the script at bottom of page.
There is also a confirmation code sample included in the same project which follows the same code as in the code sample above.
Skip links
Skip links are a means of bypassing blocks of content that is repeated on multiple web pages.
Note
The index page anchor is the form, to access the check boxes press tab
In this code sample, styling is done in site.css
.skiplink {
position: absolute;
left: 5px;
top: 0;
transform: translateY(-100%);
transition: transform 0.3s;
background: #cff4fc;
color: black;
padding: 20px;
}
.skiplink:focus {
transform: translateY(0);
color: white;
}
We setup a link in _Layout.cshtml
<body>
<header>
<nav class="navbar navbar-expand-sm . . .">
<div class="container">
<a class="skiplink mb-4 text-dark" style="text-decoration: none;"
id="skipper"
asp-fragment="main-content"
aria-label="Skip to content">
Skip to content <img src="images/key-enter1.png" alt="Press enter to skip to main content"/>
</a>
In the above example an image of a keyboard enter key is used.
In each page add the following JavaScript which when pressing ALT + 0 causes the skip link to appear.
$(this).on('keydown', function (event) {
if (event.key === '0' && event.altKey) {
$("#skipper").focus();
}
});
Optionally place the above code in a centeral JavaScript class.
Highlight current page in navigation
Suppose someone can into a site not from the main page, without the current menu item being highlighted they may not know where they are at.
To solve this, in site.js
add the following and adjust colors to suite you needs.
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.nav-link').forEach(link => {
link.classList.remove('text-dark');
link.classList.remove('bg-primary');
if (link.getAttribute('href').toLowerCase() === location.pathname.toLowerCase()) {
link.classList.add('text-white');
link.classList.add('bg-primary');
} else {
link.classList.add('text-dark');
}
});
})
Floating labels
π Documentation
In this example css
is used to change the height of the floating label. Otherwise the code for floating labels is generic as per the official example using Razor Pages for accepting and posting.
.form-floating > .form-control,
.form-floating > .form-control-plaintext {
padding: 0rem 0.75rem;
}
.form-floating > .form-control,
.form-floating > .form-control-plaintext,
.form-floating > .form-select {
height: calc(2.5rem + 2px);
line-height: 1;
}
.form-floating > label {
padding: 0.5rem 0.75rem;
}
Input-group
π documentation
Note
Easily extend form controls by adding text, buttons, or button groups on either side of textual inputs, custom selects, and custom file inputs.
For configurations such as 'UserName' below we can use tag helpers to hook-up to backend properties of a page model but this is not the case with 'Agree to terms', in this case we must get the value of the checkbox using JavaScript.
Setup a hidden element
<input type="hidden"
asp-for="Person.AgreeToTerms"
id="agreeValue" />
On form post get the value by element id than assign to the hidden element.
<script>
$('form').submit(function (e) {
document.getElementById("agreeValue").value =
document.getElementById("agreeToTerms").checked;
});
</script>
Offcanvas
Build hidden sidebars into your project for navigation, shopping carts, and more with a few classes and our JavaScript plugin.
π Offcanvas
In the code sample provided learn how to use dynamic content for off canvas along with using a toggle to open/close an off canvas from top, left, right and bottom. Also how to using button to navigate to other pages.
Popovers
Although adding popovers when ready the Bootstrap documenation seems easy, many developers get it wrong which is the reason for this example.
Steps to setup for popovers
- Include popper.js by adding via add client library
- In _Layout.cshtml add a reference to popper library before any references to Bootstrap
- In a page using popovers, you must intialize them manually
@section scripts{
<script>
$(document).ready(function(e) {
var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
var popoverList = popoverTriggerList.map(function(popoverTriggerEl) {
return new bootstrap.Popover(popoverTriggerEl);
});
</script>
}
Extra
Note the use of a skip-link, see comments in Index page.
To do a skip link in Razor Pages we can not use href but instead asp-fragment
which is the same as href="#someIdentifier"
.
When first running this app, press TAB which makes the skip link
visible, press ENTER to jump, in this case the first button on the page via the button id.
<a
class="skip-link btn btn-outline-primary mb-4"
asp-fragment="button1"
aria-label="skip to main content">
skip to main content
</a>
Documentation
π Popovers
Progress bar
The code sample provided shows how to increment a progress bar using the following JavaScript class. Feel free to rename the class.
var $ClaimsProgressbar = $ClaimsProgressbar || {};
$ClaimsProgressbar = function () {
var progressWord = "Progress";
var init = function (option) {
};
//
// Append # to incoming value to ensure it's an
// identifier
//
var assertPoundSymbol = function (value) {
if (value.charAt(0) !== "#") {
value = "#" + value;
}
return value;
};
// Increments or decrements the progress bar on all pages
var Increment = function (identifier, newValue) {
var currentValue = parseInt(newValue);
if (currentValue > 100) {
progressWord = '';
currentValue = 0;
}
$(assertPoundSymbol(identifier)).css("width", currentValue + "%").attr("aria-valuenow", currentValue).text(currentValue + "% " + progressWord);
return currentValue;
};
var CurrentValue = function (identifier) {
return parseInt($(identifier).attr("aria-valuenow"));
};
//
// Show progressbar by id e.g.
// $ClaimsProgressbar.Show('#progressStatus')
//
var Show = function (identifier) {
$(assertPoundSymbol(identifier)).show();
};
//
// Hide progressbar by id e.g.
// $ClaimsProgressbar.Hide('#progressStatus')
//
var Hide = function (identifier) {
$(assertPoundSymbol(identifier)).hide();
};
return {
init: init,
CurrentValue: CurrentValue,
Increment: Increment,
Show: Show,
Hide: Hide
};
}();
In the front end code the following code initializes the progress bar to increment by ten each time a button is clicked, reach 100 percent and it goes back to zero percent.
<script>
function IncrementProgressbar() {
var current = $ClaimsProgressbar.CurrentValue("#progressbar1");
$ClaimsProgressbar.Increment("#progressbar1", current + 10);
}
$(document).ready(function () {
$ClaimsProgressbar.init($("en").val());
$ClaimsProgressbar.Increment("#progressbar1", 0);
$ClaimsProgressbar.Show('#progressStatus');
});
</script>
Presentation layer
<script src="js/ClaimsProgressbar.js"></script>
<div class="container">
<div class="row">
<div class="col-6">
<div class="card shadow rounded-1">
<div class="card-body bg-light">
Progress bar
</div>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-8">
<div class="progress mt-2" style="height: 19px;" id="progressStatus">
<div class="progress-bar" id="progressbar1"
role="progressbar"
style="width: 0%; height: 18px;"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100">
0%
</div>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-8">
<button class="btn btn-primary mt-2" onclick="IncrementProgressbar()">Increment progress bar</button>
</div>
</div>
</div>
Spinners
π documentation
This code sample shows how to test drive spinners which are not for buttons per-say, the button allows testing spinners out without a load on a page to see what they look like.
The button wraps a span
which has a class d-none which gets removed to show the spinner for five seconds than hide the spinner with d-done
.
$(document).ready(function () {
$(() => {
$('button').on('click',
e => {
let spinner = $(e.currentTarget).find('span');
spinner.removeClass('d-none');
setTimeout(_ => spinner.addClass('d-none'), 5000);
});
});
});
<button class="btn btn-primary w-25 mt-2" type="button">
<span class="d-none spinner-grow spinner-grow-sm" role="status" aria-hidden="true"></span>
Click me...
</button>
Typical usage
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
Toast
Push notifications to your visitors with a toast, a lightweight and easily customizable alert message.
π documentation
In the provided code, the page above, data is obtained from the index page and posted to the page shown above.
Tool tips
Tooltips can be useful and n the code sample each tooltip has a different color background based on JavaScript data-color
attribute.
<div class="container">
<div style="margin-left: 30em">
<div class="row w-25 mt-lg-5">
<button type="button"
class="btn btn-primary mb-2"
data-color="primary" data-bs-toggle="tooltip"
data-bs-placement="top"
title="Tooltip on top">
Tooltip on top
</button>
</div>
<div class="row w-25">
<button type="button"
class="btn btn-secondary mb-2"
data-color="secondary"
data-bs-toggle="tooltip"
data-bs-placement="right"
title="Tooltip on right">
Tooltip on right
</button>
</div>
<div class="row w-25 mb-5">
<button type="button"
class="btn btn-success"
data-color="success"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
title="Tooltip on bottom">
Tooltip on bottom
</button>
</div>
<div class="row w-25">
<button type="button"
class="btn btn-warning"
data-color="warning"
data-bs-toggle="tooltip"
data-bs-placement="left"
title="Tooltip on left">
Tooltip on left
</button>
</div>
<div class="row w-25 mt-2">
<input class="form-control"
type="text"
data-color="primary"
data-bs-toggle="tooltip"
data-bs-placement="left"
title="Enter something" />
</div>
</div>
</div>
@section Scripts
{
<script type="text/javascript">
// init tool-tips
// change tool-tip color using data-color
(function (window, document, $, undefined) {
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
$("button, input").hover(function () {
$(".tooltip-inner").css({ "background-color": "var(--bs-" + $(this).data("color") + ")" });
});
})(window, document, jQuery);
</script>
}
Note uncomment the following line to change arrow colors
<link rel="stylesheet" href="css/tips.css"/>
Which points to
.tooltip-inner {
min-width: 14em;
}
.tooltip.bs-tooltip-top .tooltip-arrow::before {
border-top-color: gainsboro;
}
.tooltip.bs-tooltip-bottom .tooltip-arrow::before {
border-bottom-color: gainsboro;
}
.tooltip.bs-tooltip-start .tooltip-arrow::before {
border-left-color: gainsboro;
}
.tooltip.bs-tooltip-end .tooltip-arrow::before {
border-right-color: gainsboro;
}