HTML preprocessors can make writing HTML more powerful or convenient. For instance, Markdown is designed to be easier to write and read for text documents and you could write a loop in Pug.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. So you don't have access to higher-up elements like the <html>
tag. If you want to add classes there that can affect the whole document, this is the place to do it.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. If you need things in the <head>
of the document, put that code here.
The resource you are linking to is using the 'http' protocol, which may not work when the browser is using https.
CSS preprocessors help make authoring CSS easier. All of them offer things like variables and mixins to provide convenient abstractions.
It's a common practice to apply CSS to a page that styles elements such that they are consistent across all browsers. We offer two of the most popular choices: normalize.css and a reset. Or, choose Neither and nothing will be applied.
To get the best cross-browser support, it is a common practice to apply vendor prefixes to CSS properties and values that require them to work. For instance -webkit-
or -moz-
.
We offer two popular choices: Autoprefixer (which processes your CSS server-side) and -prefix-free (which applies prefixes via a script, client-side).
Any URLs added here will be added as <link>
s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.
You can apply CSS to your Pen from any stylesheet on the web. Just put a URL to it here and we'll apply it, in the order you have them, before the CSS in the Pen itself.
You can also link to another Pen here (use the .css
URL Extension) and we'll pull the CSS from that Pen and include it. If it's using a matching preprocessor, use the appropriate URL Extension and we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
Any URL's added here will be added as <script>
s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
Search for and use JavaScript packages from npm here. By selecting a package, an import
statement will be added to the top of the JavaScript editor for this package.
Using packages here is powered by esm.sh, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ESM usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
<h1>FAQ using <abbr title="Accessible Rich Internet Applications">ARIA</abbr> roles to meet <abbr title="Web Content Accessibility Guidelines">WCAG</abbr> 2 level AA</h1>
<dl class="dl-faq pab_container">
<dt data-pab=faq_1><span>Is the content fully hidden?</span></dt>
<dd id=faq_1>
<div>
<p>Yes.</p>
<p>This content is hidden both visually and aurally, and doesn't appear in the keychain until the button is activated (expanded = true).</p>
</div>
</dd>
<!-- adding data-pab-expand will force section open by default -->
<dt data-pab=faq_2 data-pab-expand><span>May an answer be displayed by default?</span></dt>
<dd id=faq_2>
<div>
<p>Apparently yes.</p>
<p>This content is available by default until the button deactivates it (expanded = false) which removes it visually, aurally and from the keychain.</p>
<p>Add the attribute <code>data-pab-expand</code> to the <code>dt</code> to open it by default.</p>
</div>
</dd>
<dt data-pab=faq_3><span>Can an answer be opened by <abbr title="Universal Resource Location">URL</abbr> reference?</span></dt>
<dd id=faq_3>
<div>
<p>Yes.</p>
<p>Any question may be expanded on page load by referencing its <code>id</code> in the URL.</p>
<p>For example this content could be automatically opened by adding "#faq_3" to the URL in the address bar like so:<br><a target=_blank title="[new window]" href="https://codepen.io/2kool2/pen/ZOkojB#faq_3">https://codepen.io/2kool2/pen/ZOkojB#faq_3</a></p>
<p>The focus caret is moved to the activation button when referenced in this manner.</p>
</div>
</dd>
<dt id=q_4 data-pab=faq_4><span>Can an anchor scroll to a question and then open the answer?</span></dt>
<dd id=faq_4>
<div>
<p>Yes, though it only opens on first click. After which it simply scrolls the question to the top of the viewport.</p>
<p>Add an unique <code>id</code> value to the question <code>dt</code> then reference the <code>id</code> in the anchors <code>href</code>.</p>
</div>
</dd>
<dt data-pab=faq_5><span>Will it work with the font size scaled-up 200%?</span></dt>
<dd id=faq_5>
<div>
<p>Yes.</p>
<p>The height of a hidden block is calculated when the activaton button is pressed. It's also recalculated when the browser window is resized.</p>
<p>In fact this module should easily scale to 300%, limited only by the display width and word length.</p>
</div>
</dd>
</dl>
<div id=injection_point></div>
<p>Can an anchor <a href="#q_4">open an answer</a> from just an id reference?</p>
<h2>How it works</h2>
<p>Takes a <code>dl</code> list and wraps each of its <code>dt</code> content with a <code>button</code>. The <code>dt</code> is targeted by the <code>data-pab</code> attribute which has the value of the <code>id</code> of the <code>dd</code> to show or hide.</p>
<p>Adding the attribute <code>data-pab-expand</code> to a <code>dt</code> will make a <code>dd</code> open by default.</p>
<p>If a <code>dd</code> <code>id</code> value is referenced as a fragment identifier in the <abbr title="Uniform Reference Locator">URL</abbr>, then it is also opened by default.</p>
<p>The code is very semantically written but has one caveat. It only allows a single <code>dd</code> per <code>dt</code> which is fine for an FAQ (but not so for a dictionary).</p>
<p>Note to self: Don't use "Lorem Ipsum" as place-holder text when sending for screen-reader testing. I nearly cried when I heard the gibberish spoken by Jaws today. Marking the block with a <code>lang="la"</code> may be technically correct but hardly aids comprehension.</p>
<p>GitHub repo: <a target=_blank title="[new window]" href="https://github.com/2kool2/accessible-faq">accessible-faq</a></p>
<br>
<svg style="display:none">
<defs>
<symbol viewBox="0 0 38 38" id="icon-plus">
<path class="icon-plus-v" d="M10.5 19l17 0"></path>
<path class="icon-plus-h" d="M19 10.5l0 17"></path>
</symbol>
<symbol viewBox="0 0 38 38" id="icon-minus">
<path class="icon-plus-v" d="M10.5 19l17 0"></path>
</symbol>
<!-- vert and hori combined make up the plus icon and allow for animation -->
<symbol viewBox="0 0 38 38" id="icon-vert">
<path d="M19 10.5l0 17"></path>
</symbol>
<symbol viewBox="0 0 38 38" id="icon-hori">
<path d="M10.5 19l17 0"></path>
</symbol>
</defs>
</svg>
[[[https://codepen.io/2kool2/pen/mKeeGM]]]
body{
padding: 0;
font-weight:100;
scroll-behavior: smooth;
}
/* FAQ container */
.dl-faq {
position: relative;
max-width: 36rem;
margin: 2rem auto 3rem;
}
.dl-faq > dt {
font-size: 1.2rem;
font-weight: 100;
padding: 1rem;
/* Fix for IE9 & 10 */
border-top: 1px solid rgba(255,255,255,.2);
}
dt > button {
color: inherit;
background-color: inherit;
}
.dl-faq > dt:first-child .pab-btn,
.dl-faq > dt:first-child {
border-top: 0;
}
.dl-faq.pab_container > dt {
/* added via JS */
padding: 0;
}
.dl-faq > dd {
margin: 0 auto;
padding: 0 1.5em;
font-weight:100;
}
.dl-faq > dd > div {
padding: 0 0 2rem;
}
.dl-faq div > p {
margin: 0 0 1rem;
}
.dl-faq div >:last-child {
margin: 0;
}
/* The acivating buttons added via JS */
.pab-btn {
position: relative;
cursor: pointer;
transition: color .3s ease-in;
/* Using absolute positioning for SVG so reserve some space */
padding: 1rem 2.5rem 1rem .5rem;
border: 0 solid transparent;
border-top: 1px solid rgba(0,0,0,.75);
/* inherit doesn't work in IE */
font-size: inherit;
text-align: left;
width: 100%;
}
.pab-btn:hover,
.pab-btn:focus,
.pab-btn:active {
color:#fff;
background-color: rgba(0,0,0,.25);
}
.pab-btn:focus {
outline: 0 solid;
}
.pab-btn::-moz-focus-inner {
border: 0;
padding: 0;
margin-top: -2px;
margin-bottom: -2px;
}
/* Underline text on button hover (Tesco requirement) */
.pab-btn > span {
position: relative;
/* Removes button drepression in IE */
pointer-events: none;
/* Required by Safari */
border-bottom: 1px solid transparent;
transition: border-color .3s;
}
.pab-btn:hover > span,
.pab-btn:focus > span {
border-bottom-color: rgba(255,255,255,.5);
}
.pab-btn:active > span {
border-bottom-color: transparent;
}
/* SVG plus */
.pab-svg-plus {
/* Tesco requirement
border: 2px solid currentColor; */
border-radius: 100%;
display: block;
position: absolute;
top: calc(50% - .75em);
right: 4px;
width: 1.5em;
height: 1.5em;
margin: 0;
pointer-events: none;
stroke-width: 4;
stroke-linecap: square;
stroke: currentColor;
-webkit-transition: transform .7s ease-out, box-shadow .3s ease-out;
transition: transform .7s ease-out, box-shadow .3s ease-out;
}
.pab-btn:hover .pab-svg-plus,
.pab-btn:focus .pab-svg-plus {
/* Same colour as text but with .4 alpha */
/* Tesco requirement
box-shadow: 0 0 0 4px rgba(0, 83, 159, 0.4);*/
}
.pab-btn:active .pab-svg-plus {
/* Tesco requirement
box-shadow: 0 0 0 4px rgba(0, 83, 159, 0);*/
}
[aria-expanded="true"] > .pab-svg-plus {
transform: rotateZ(360deg);
}
.use-plus {
/* used to animate plus into minus */
-webkit-transition: stroke .5s ease-out, opacity .7s ease-out;
transition: stroke .5s ease-out, opacity .7s ease-out;
}
[aria-expanded=true] .use-plus {
opacity: 0;
}
.isSafari .pab-btn .pab-svg-plus {
box-shadow: none;
}
/* Open / close animation - The inaccurate CSS max-height is resolved via JS adding an inline style */
[data-pab] + [aria-hidden] {
overflow: hidden;
opacity: 1;
max-height: 50rem;
visibility: visible;
transition: visibility 0s ease 0s, max-height .65s ease-out 0s, opacity .65s ease-in 0s;
}
[data-pab] + [aria-hidden="true"] {
max-height: 0;
opacity: 0;
visibility: hidden;
transition-delay: .66s, 0s, 0s;
}
/* Overide the max-height set as an inline style by the JS */
[data-pab] + [style][aria-hidden="true"] {
max-height: 0 !important;
}
// https://john-dugan.com/javascript-debounce/
// https://codepen.io/johndugan/pen/BNwBWL?editors=001
var debounce = function(func, wait, immediate) {
'use strict';
var timeout;
return function() {
var context = this;
var args = arguments;
var later = function() {
timeout = null;
if ( !immediate ) {
func.apply(context, args);
}
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait || 200);
if ( callNow ) {
func.apply(context, args);
}
};
};
// peek-a-boo.7.3.js - Mike Foskett - https://websemantics.uk/articles/peek-a-boo-v7/
// Show - hide a block - adapted for FAQ
// Requires:
// setAttribute / getAttribute (IE9+)
// classList (IE10+) - disabled
// addEventListener (IE9+)
// requestAnimationFrame (IE10+) - replace with requestAF() for IE9
// querySelectorAll
// preventDefault
// debounce()
// FAQ version:
// v7.4 Added: open an question from an internal anchor
// v7.3 Expanded when URI fragment matches the target ID
// v7.2 HTML button reinstated, js adjusted.
// Initial open/close state reworked
var Pab = (function (window, document, debounce) {
// Terminology used:
// toggle - The dynamically added button used to toggle the hidden content
// target - The object which contains the hidden content
// toggleParent - The object which will, or does, contain the toggle button
'use strict';
var dataAttr = 'data-pab';
var attrName = dataAttr.replace('data-', '') + '_';
var btnClass = dataAttr.replace('data-', '') + '-btn';
var dataExpandAttr = dataAttr + '-expand';
var internalId = 1;
function $ (selector) {
return Array.prototype.slice.call(document.querySelectorAll(selector));
}
function _isExpanded (obj) { // or not aria-hidden
return obj && (obj.getAttribute('aria-expanded') === 'true' || obj.getAttribute('aria-hidden') === 'false');
}
// This function is globally reusable. Perhaps externalise for reuse?
// Get height of an element object
// Assumes it is hidden by max-height: 0 in the CSS
var _getHiddenObjectHeight = function (obj) {
obj.setAttribute('style', 'max-height: none');
var height = obj.scrollHeight;
obj.removeAttribute('style');
return height;
};
/* Not enough support to be truly useful.
Under most circumstance aria-expanded is sufficient.
var _setToggleSvgTitle = function(toggle) {
var title = toggle.getElementsByTagName('title');
if (title && title[0]) {
title[0].innerHTML = _isExpanded(toggle) ? 'Hide' : 'Show';
}
};
*/
var _openCloseToggleTarget = function (toggle, target, isExpanded) {
toggle.setAttribute('aria-expanded', !isExpanded);
_setToggleMaxHeight(target);
window.requestAnimationFrame(function(){
target.setAttribute('aria-hidden', isExpanded);
});
// _setToggleSvgTitle(toggle); - not enough support to be useful
};
/* // deprecated to support IE 9 FAQ
var _toggleParentClass = function (event) {
var cls = attrName + 'btn_';
switch (event.type) {
case 'focus' :
//event.target.parentNode.classList.add(cls + 'focused');
event.target.parentNode.className += ' ' + cls + 'focused';
break;
case 'blur' :
event.target.parentNode.classList.remove(cls + 'focused');
break;
case 'mouseover' :
event.target.parentNode.classList.add(cls + 'hovered');
break;
case 'mouseout' :
event.target.parentNode.classList.remove(cls + 'hovered');
break;
}
};
*/
var _setToggleMaxHeight = function (target) {
if (_isExpanded(target)) {
// max-height overidden by CSS !important
// target.style.maxHeight = 0;
} else {
target.style.maxHeight = _getHiddenObjectHeight(target) + 'px';
}
};
var _toggleClicked = function (event) {
var toggle = event.target;
var target;
var isExpanded;
if (toggle) {
// To prevent children bubbling up to parent causing more than one click event
event.stopPropagation();
target = document.getElementById(toggle.getAttribute('aria-controls'));
if (target) {
isExpanded = _isExpanded(toggle);
_openCloseToggleTarget(toggle, target, isExpanded);
}
}
};
var _addToggleListeners = function (toggle) {
// Simpler to mangage here rather than in a global handler (consider hover and blur)
// Parent of toggle and target - Deprecated to support IE 9
//toggle.addEventListener('focus', _toggleParentClass, false);
//toggle.addEventListener('blur', _toggleParentClass, false);
//toggle.addEventListener('mouseout', _toggleParentClass, false);
//toggle.addEventListener('mouseover', _toggleParentClass, false);
toggle.addEventListener('click', _toggleClicked, false);
};
var _setUpToggle = function (toggle) {
// Create a html button, add content from parent, replace original parent content.
var btn = document.createElement('button');
btn.className = btnClass;
btn.innerHTML = toggle.innerHTML;
btn.setAttribute('aria-expanded', false);
btn.setAttribute('id', attrName + internalId++);
btn.setAttribute('aria-controls', toggle.getAttribute(dataAttr));
toggle.innerHTML = '';
toggle.appendChild(btn);
return btn;
};
// Prestating the container class in the HTML allows the CSS to render before JS kicks in.
// Add container class to parent if not prestated
var _setUpToggleParent = function (toggle) {
var parent = toggle.parentElement;
if (parent && !parent.className.match(attrName + 'container')) {
//parent.classList.add(attrName + 'container');
parent.className += ' ' + attrName + 'container';
}
};
var _addToggleSVG = function (toggle) {
var clone = toggle.cloneNode(true);
if (!clone.innerHTML.match('svg')) {
// HTML SVG definition allows more control
clone.innerHTML += '<svg role=presentational focusable=false class=' + dataAttr.replace('data-', '') + '-svg-plus><use class=\"use-plus\" xlink:href=\"#icon-vert\" /><use xlink:href=\"#icon-hori\"/></svg>';
//requestAnimationFrame(function () {
toggle.parentElement.replaceChild(clone, toggle);
//});
}
return clone;
};
var _setUpTargetAria = function (toggle, target) {
target.setAttribute('aria-hidden', !_isExpanded(toggle));
target.setAttribute('aria-labelledby', toggle.id);
};
var _resetAllTargetsMaxHeight = function () {
var toggles = document.querySelectorAll('[' + dataAttr + ']');
var i = toggles.length;
var target;
while (i--) {
target = document.getElementById(toggles[i].getAttribute(dataAttr));
if (target) {
target.style.maxHeight = _getHiddenObjectHeight(target) + 'px';
}
}
};
var isMustardCut = function () {
return (document.querySelectorAll && document.addEventListener);
};
var _openIfRequired = function (toggle, target) {
var fragmentId = window.location.hash.replace('#', '');
// Expand by default 'data-pab-expand' small delay applied
if (toggle.parentElement.hasAttribute(dataExpandAttr)) {
setTimeout(function () {
_openCloseToggleTarget(toggle, target, _isExpanded(toggle));
}, 500);
}
// Check url fragment and if target ID matches, open it
if (target.id === fragmentId) {
setTimeout(function () {
_openCloseToggleTarget(toggle, target, false);
toggle.focus();
}, 500);
}
};
var addSingleToggleTarget = function (toggleParent) {
var targetId = toggleParent.getAttribute(dataAttr);
var target = document.getElementById(targetId);
var toggle;
if (target && isMustardCut) {
toggle = _setUpToggle(toggleParent);
_setUpToggleParent(toggleParent);
toggle = _addToggleSVG(toggle);
_setUpTargetAria(toggle, target);
_addToggleListeners(toggle);
_openIfRequired(toggle, target);
}
};
var hashChanged = function (e) {
var fragmentId = window.location.hash.replace('#', '');
var toggle = document.querySelector('#' + fragmentId + ' > .' + btnClass);
var target = document.getElementById(toggle.getAttribute('aria-controls'));
if (!toggle || !target) {return false;}
toggle.focus();
toggle.scrollIntoView({behavior: 'smooth', block: 'start', inline: 'nearest'});
_openCloseToggleTarget(toggle, target, false);
};
var addToggles = function () {
// Iterate over all toggles (elements with the 'data-pab' attribute)
var togglesMap = $('[' + dataAttr + ']').reduce(function (temp, toggleParent) {
addSingleToggleTarget(toggleParent);
return true;
}, {});
return true;
};
if (isMustardCut) {
window.addEventListener('load', addToggles, false);
// Recalculate all target max-heights after (debounced) window is resized.
window.addEventListener('resize', debounce(_resetAllTargetsMaxHeight, 500), false);
// On fragment change
window.addEventListener('hashchange', hashChanged, false);
}
return {
// Exposes an addition function to the global scope allowing toggle & target to be added dynamically.
add: addSingleToggleTarget
};
}(window, document, debounce));
// To add dynamically created toggles:
// Pab.add(toggle-object); // Add individual toggle & target
// setTimeout(function(){
// document.querySelector('.pab_container').innerHTML += `
// <dt data-pab=faq_6><span>Test dynamic insertion</span></dt>
// <dd id=faq_6>
// <div>
// <p>Dynamically added to <code>dl</code>.</p>
// </div>
// </dd>`;
// Pab.add(document.querySelector('[data-pab=faq_6]'));
// }, 2000);
// setTimeout(function(){
// document.getElementById('injection_point').innerHTML += `
// <div data-pab=faq_7><span>Test dynamic insertion</span></div>
// <div id=faq_7>
// <div>
// <p>Dynamically added externally to the <code>dl</code>.</p>
// </div>
// </div>`;
// Pab.add(document.querySelector('[data-pab=faq_7]'));
// }, 2000);
Also see: Tab Triggers