Web UI has been evolving faster than ever
Separate your application logic from your styling logic
Stop writing unnecessary, heavy, thread-blocking JavaScript
popover attribute
popover
attributeBuilt-in accessibility via keyboard behavior, tab focus management, top-layer support, and (optional) light-dismiss
May 2023
September 2023
May 2024 (soon)
<!-- Button -->
<button popovertarget="my-popover">
Open Me
</button>
<!-- Popover -->
<div id="my-popover" popover>
... Popover Stuff ...
</div>
<button popovertarget="my-popover">
Open Popover
</button>
<div id="my-popover" popover>
<p>I am a popover with more information.<p>
</div>
<button popovertarget="my-popover">
Open Popover
</button>
<div id="my-popover" popover="manual">
<button class="close-btn"
popovertarget="my-popover"
popovertargetaction="hide">
<span aria-hidden=”true”>❌</span>
<span class="sr-only">Close</span>
</button>
<p>I am a popover with more information.<p>
</div>
.settings-popover {
&:popover-open {
/* 0. BEFORE-OPEN */
@starting-style {
transform: translateY(20px);
opacity: 0;
}
/* 1. OPEN STATE */
transform: translateY(0);
opacity: 1;
}
/* 2. EXIT STATE */
transform: translateY(-50px);
opacity: 0;
/* Transitioning properties + allow-discrete */
transition: transform 0.5s,
opacity 0.5s,
display 0.5s allow-discrete;
}
// JavaScript
deleteListItem() {
const listItem = this.parentNode;
listItem.classList.add('is-deleting');
setTimeout(() => {
listItem.parentNode.removeChild(listItem);
}, 500);
}
/* CSS */
.item {
opacity: 1;
transform-origin: bottom;
transition: opacity 0.5s,
transform 0.5s,
display 0.5s allow-discrete;
}
@starting-style {
.item {
opacity: 0;
height: 0;
}
}
/* while deleting, before DOM removal in JS */
.is-deleting {
opacity: 0;
display: none;
transform: skewX(50deg) translateX(-25vw);
}
<!-- Add an id -->
<button popovertarget="my-popover" id="toggle-btn">
Open Me
</button>
<!-- Add an anchor -->
<div popover id="my-popover" anchor="toggle-btn">
Popover Stuff
</div>
#profile-settings-popover {
bottom: calc(anchor(top) + var(--spacer));
right: calc(anchor(right));
}
#profile-settings-popover {
inset-area: top span-left;
}
una.github.io/anchor-tool/
[popover] {
position: absolute;
inset-area: top span-left;
margin-bottom: 1rem;
}
.item {
--radius: calc(var(--btn-size) + var(--extra-space));
background-color: var(--bg);
transform: translateX(calc(cos(var(--angle)) * var(--radius)))
translateY(calc(sin(var(--angle) * -1) * var(--radius)));
transition: transform 0.3s var(--delay) ease;
}
popover
has no default semantic rolerole="menu"
role="tooltip"
dialog
elementselectlist
element 👩🏼🔬select
v2A selectable list of options you can fully customize, with both style and content, building on the existing select element
❌
❌
❌
Stylable, accessible dropdowns are coming!!!
Search by region
<select class="country-select">
<button>
<selectedoption></selectedoption>
</button>
<datalist>
<option value="" hidden>
<p>Select a country</p>
</option>
<option value="andorra">
<img src="../Andorra.svg" />
<p>Andorra</p>
</option>
<option value="bolivia">
<img src="../Bolivia.svg.png" />
<p>Bolivia</p>
</option>
...
</datalist>
</select>
/* Fallback for older browsers */
<select class="country-select">
<button>
<selectedoption></selectedoption>
</button>
<datalist>
<option value="" hidden>
<p>Select a country</p>
</option>
<option value="andorra">
<img src="../Andorra.svg" />
<p>Andorra</p>
</option>
<option value="bolivia">
<img src="../Bolivia.svg.png" />
<p>Bolivia</p>
</option>
...
</datalist>
</select>
view-timeline
animation-timeline
view()
May 2023
❌
Behind Flag
@keyframes fly-in {
0% {
opacity: 0;
transform: translateX(-100px);
}
50% {
opacity: 1;
transform: translateX(0px);
}
}
blockquote {
animation: fly-in auto linear;
animation-timeline: view();
}
document.querySelectorAll('.delete-btn')
.forEach(btn => {
btn.addEventListener('click', () => {
btn.parentElement
.classList.add('fade-out');
}
)
})
document.querySelectorAll('.delete-btn')
.forEach(btn => {
btn.addEventListener('click', () => {
if (document.startViewTransition) {
document.startViewTransition(() => {
btn.parentElement.remove();
});
} else {
btn.parentElement.remove();
}
}
)
})
:has()
relational selectorStyle parent elements (and more) based on the presence or state of child elements
Aug 2022
Mar 2022
December 2023
:has()
relational selectorStyle based on the number of children
:has(:nth-child(n + x))
@supports selector(:has(a)) {
.post {
display: none;
}
.filter-bar:has(#css-tag:checked) ~ .posts .post:has([data-tag="CSS"]),
.filter-bar:has(#html-tag:checked) ~ .posts .post:has([data-tag="HTML"]),
.filter-bar:has(#js-tag:checked) ~ .posts .post:has([data-tag="JavaScript"]),
... {
display: block;
}
}
@property
declaration
@property --colorPrimary {
syntax: '<color>';
initial-value: magenta;
inherits: false;
}
@property
Typed CSS
.card {
background-color: var(--colorPrimary); /* magenta */
}
.highlight-card {
--colorPrimary: yellow;
background-color: var(--colorPrimary); /* yellow */
}
.another-card {
--colorPrimary: 23;
background-color: var(--colorPrimary); /* magenta */
}
@property --gradPoint {
syntax: '<percentage>';
inherits: false;
initial-value: 40%;
}
.post {
background:
linear-gradient(var(--color1) var(--gradPoint),
var(--color2) calc(var(--gradPoint) + 20%));
transition: --gradPoint 0.5s;
}
.post:hover, .post:focus {
--gradPoint: 100%;
}
Went Stable Cross-browser recently
Old
@media (max-width: 400px) {
...
}
@container (min-inline-size: 300px)
and (max-inline-size: 599px) {
...
}
New
@media (width <= 400px) {
...
}
@container (300px <= inline-size < 600px) {
...
}
This:
.card {
/* card styles */
}
.card .title {
/* element styles */
}
.card:hover {
/* modifier styles */
}
.card:focus {
/* modifier styles */
}
@container (width >= 300px) {
.card {
/* CQ styles */
}
}
Becomes...
.card {
/* card styles */
.title {
/* element styles */
}
&:hover, &:focus {
/* modifier styles */
}
@container (width >= 300px) {
/* CQ styles */
}
}
🤫 This presentation is fully built with vanilla CSS & HTML*
*with some iframes for demos due to laziness
<style>
with display:block
and contenteditable
scroll-snap
accesskey
Leverage the power of the browser
Reduce complexity, improve velocity
Stay in the know