The CSS You Don't Know About

(a talk by @AhoyLemon)

The CSS You Don't Know About

(a talk by @AhoyLemon)

The CSS You Don't Know About

(a talk by @AhoyLemon)

Hi!

My name is Lemon.

(I make websites.)

I work at

Savas Labs

Front End Director

  • Mentoring
  • Shepherding
  • Documenting

I have stickers!

So, what's new in CSS?

CSS has variables!!!

CSS has had variables since 2018.

The CSS You Don't Know About*

* Okay Maybe You Know About Some Of This, I Don't Know... I Don't Know What Blogs You Read.

The CSS You Don't Know About*

* Okay Maybe You Know About Some Of This, I Don't Know... I Don't Know What Blogs You Read. So Maybe Some Of This Is Familiar, But I Bet Not All Of It. Look: The Point Is I'm Going To Show You Some Features In Modern CSS, And It's Highly Likely Some Of This Information Is New.

The CSS You Don't Know About*

* Okay Maybe You Know About Some Of This, I Don't Know... I Don't Know What Blogs You Read. So Maybe Some Of This Is Familiar, But I Bet Not All Of It. Look: The Point Is I'm Going To Show You Some Features In Modern CSS, And It's Highly Likely Some Of This Information Is New. But Either Way You're Going To Learn Some New Stuff About CSS Or At Least We'll Have Fun Together And That's Okay Too.

Anyway, we were talking about variables

This has been in Sass for about 20 years...

$white: #eee;
$black: #313131;
$orange: #e98741;
$red: #ee2a2a;
$white: #eee;
$black: #313131;
$orange: #e98741;
$red: #ee2a2a;
main {
  background:$white;
  color:$black;
}
button {
  background: $orange;
  color: $white;
}
$white: #eee;
$black: #313131;
$orange: #e98741;
$red: #ee2a2a;
main {
  background:$white;
  color:$black;
}
button {
  background: $orange;
  color: $white;
}
button.warning {
  background: $black;
  color: $red;
}
$white: #eee;
$black: #313131;
$orange: #e98741;
$red: #ee2a2a;

$background-color: $white;
$text-color: $black;
$link-color: $orange;
$warning-color: $red; 

main {
  background: $background-color;
  color: $text-color;
}
button {
  background: $link-color;
  color: $background-color;
}
button.warning {
  background: $text-color;
  color: $warning-color;
}

But let's do the same thing in CSS...

:root {
  --white: #eee;
  --black: #313131;
  --orange: #e98741;
  --red: #ee2a2a;
}
:root {
  --white: #eee;
  --black: #313131;
  --orange: #e98741;
  --red: #ee2a2a;
}
main {
  background: var(--white);
  color: var(--black);
}
button {
  background: var(--orange);
  color: var(--white);
}
:root {
  --white: #eee;
  --black: #313131;
  --orange: #e98741;
  --red: #ee2a2a;

  --body-color: var(--white);
  --text-color: var(--black);
  --link-color: var(--orange);
  --warning-color: var(--red); 
}
main {
  background-color: var(--body-color);
  color: var(--text-color);
}
button {
  background-color: var(--link-color);
}
button {
  background-color: var(--button-bg);
  color: var(--button-fg);
  border-color: var(--button-border-color);
  font-size: var(--button-font-size);
}
button.secondary {
  --button-bg: var(--light-grey);
}
button.warning {
  --button-bg: var(--warning-color);
  --button-fg: var(--black);
}
button.transparent {
  --button-bg: transparent;
}
button.huge {
  --button-font-size: 2rem;
}

Let's go crazy!

:root {
  --background-color: #ffffff;
  --text-color: #000000;
  --primary-color: #1e7e34;
  --secondary-color: #4cae4c;
}
[data-scheme="warm"] {
  --primary-color: #b3542e;
  --secondary-color: #a12e2b;
  button {
    --text-color: #eee;
  }
}
[data-scheme="cool"] {
  --primary-color: #17a2b8;
  --secondary-color: #5bc0de;
}
[data-scheme="grayscale"] {
  --primary-color: #808080;
  --secondary-color: #a9a9a9;
}
[data-theme="dark"] {
  --background-color: #343a40;
  --text-color: #f8f9fa;
}
[data-theme="dark"][data-scheme="warm"] {
  --primary-color: #ff7f50;
  --secondary-color: #ff6347;
  button {
    --text-color: #111;
  }
}
[data-theme="dark"][data-scheme="cool"] {
  --primary-color: #1ba5b8;
  --secondary-color: #5fc8d4;
}
[data-theme="dark"][data-scheme="grayscale"] {
  --primary-color: #b0b0b0;
  --secondary-color: #bcbcbc;
  button {
    --text-color: #222;
  }
}
:root {
  --background-color: #ffffff;
  --text-color: #000000;
  --primary-color: #1e7e34;
  --secondary-color: #4cae4c;
}
[data-scheme="warm"] {
  --primary-color: #b3542e;
  --secondary-color: #a12e2b;
  button {
    --text-color: #eee;
  }
}
[data-scheme="cool"] {
  --primary-color: #17a2b8;
  --secondary-color: #5bc0de;
}
[data-scheme="grayscale"] {
  --primary-color: #808080;
  --secondary-color: #a9a9a9;
}
[data-theme="dark"] {
  --background-color: #343a40;
  --text-color: #f8f9fa;
}
[data-theme="dark"][data-scheme="warm"] {
  --primary-color: #ff7f50;
  --secondary-color: #ff6347;
  button {
    --text-color: #111;
  }
}
[data-theme="dark"][data-scheme="cool"] {
  --primary-color: #1ba5b8;
  --secondary-color: #5fc8d4;
}
[data-theme="dark"][data-scheme="grayscale"] {
  --primary-color: #b0b0b0;
  --secondary-color: #bcbcbc;
  button {
    --text-color: #222;
  }
}

body {
  background-color: var(--background-color);
  color: var(--text-color);
}
button {
  background-color: var(--primary-color);
  color: var(--text-color);
  border: 0.2em solid var(--secondary-color);
  transition: all 0.3s linear;
}
button:hover, button:focus-visible {
  background-color: var(--secondary-color);
  color: var(--background-color);
  border: 0.2em solid var(--primary-color);
}
a[href] {
  color: var(--primary-color);
  text-decoration: none;
  transition: color 0.3s ease;
}
a[href]:hover, a[href]:focus-visible {
  color: var(--secondary-color);
  text-decoration: underline;
}

nesting

Scss

.card {
  display: flex;
  flex-direction: column;
  padding: 1rem;
  background-color: #f8f9fa;
  h3 {
    font-size: 1.5rem;
    margin-bottom: 0.5rem;
  }
  p {
    font-size: 1rem;
    color: #6c757d;
    a {
      color: #007bff;
      text-decoration: none;
      &:hover {
        text-decoration: underline;
      }
    }
  }
  .actions {
    margin-top: 1rem;
    display: flex;
    gap: 0.5rem;
    button {
      padding: 0.5rem 1rem;
      border: none;
      cursor: pointer;
      &.primary {
        background-color: #007bff;
        color: #fff;
      }
      &.secondary {
        background-color: #6c757d;
        color: #fff;
      }
    }
  }
}

CSS

.card {
  display: flex;
  flex-direction: column;
  padding: 1rem;
  background-color: #f8f9fa;
  h3 {
    font-size: 1.5rem;
    margin-bottom: 0.5rem;
  }
  p {
    font-size: 1rem;
    color: #6c757d;
    a {
      color: #007bff;
      text-decoration: none;
      &:hover {
        text-decoration: underline;
      }
    }
  }
  .actions {
    margin-top: 1rem;
    display: flex;
    gap: 0.5rem;
    button {
      padding: 0.5rem 1rem;
      border: none;
      cursor: pointer;
      &.primary {
        background-color: #007bff;
        color: #fff;
      }
      &.secondary {
        background-color: #6c757d;
        color: #fff;
      }
    }
  }
}

Scss Differences

// SCSS uses // for comments, which is not valid in CSS.

$dark-color: #323232;
$primary-color: #007bff;
$secondary-color: #6c757d;

.block {
  display:block; 
  // .block { display:block; }
  &-element {
    padding: 1em;
    // .block-element { padding:1em; }
    &--modifier {
      color: $primary-color;
      // .block-element-modifier { color:#007bff } 
    }
  }
}

div {
  .grid& {
    display:grid;
    // .griddiv { display:grid; }
  }
}

.foo, #bar {
  .baz {
    background-color: $secondary-color;
    // .foo .baz,
    // #bar .baz { background-color:#6c757d; }
  }
}

.call-to-action .heading {
  .dark-theme & {
    background: $dark-color;
    // .dark-theme .call-to-action .heading { background: #323232 }
  }
}

.brother {
  ~ .brother {
    font-style:italic;
  }
  & ~ .sister {
    font-style:italic;
  }
  // .brother ~ .brother,
  // .brother ~ .sister { font-style-italic; }
  // (the initial & is optional)
}

CSS Differences

/* CSS uses wrapped /* for comments, which is valid in both CSS and SCSS. */

:root {
  --dark-color: #313131;
  --primary-color: #007bff;
  --secondary-color: #6c757d;
}

.block {
  display:block; 
  /* .block { display:block; } */
  &-element {
    padding: 1em;
    /* -element.block { padding:1em; } */
    &--modifier {
      color: var(--primary-color);
      /* ❌ invalid syntax */
    }
  }
}

div {
  .grid& {
    display:grid;
    /* div.grid { display:grid; } */
  }
}

.foo, #bar {
  .baz {
    background-color: var(--secondary-color);
    /* :is(.foo, #bar) .baz { background-color:#6c757d; } */
  }
}

.call-to-action .heading {
  .dark-theme & {
    background: $dark-color;
    /* .dark-theme :is(.call-to-action .heading) */
  }
}

.brother {
  ~ .brother {
    font-style:italic;
  }
  & ~ .sister {
    font-style:italic;
  }
  /* (❌ Invalid syntax - .brother ~ .brother needed the &) */
  /* .brother ~ .sister { font-style-italic; } */
  
}

HTML Family Tree

Scss Family Tree

.parent {
  > .child {} // .parent > .child
  & > .child { // .parent > .child
    &.brother { // .parent > .child.brother
      ~ .brother {} // .parent > .child ~ .brother
      & ~ .brother {} // .parent > .child ~ .brother
      & + .sister {} // .parent > .child.brother + .sister
      + .sister {} // .parent > .child + .sister
    }
    &.sister { // .parent > .child.sister
      ~ .brother {} // .parent > .child.sister ~ .brother
      & ~ .brother {} // .parent > .child.sister ~ .brother
      & + .sister {} // .parent > .child.sister + .sister
      + .sister {} // .parent > .child + .sister
    }
    & > .grandchild {} // .parent > .child > .grandchild
    .grandchild {} // .parent > .child .grandchild
  }
}

CSS Family Tree

.parent { 
  > .child {} /* ❌ Invalid — combinator requires & */
  & > .child { /* .parent > .child */
    &.brother { /* .parent > .child.brother */
      ~ .brother {} /* ❌ Invalid — combinator requires & */
      & ~ .brother {} /* .parent > .child ~ .brother */
      & + .sister {} /* .parent > .child.brother + .sister */
      + .sister {} /* ❌ Invalid — combinator requires & */
    }
    &.sister { /* .parent > .child.sister */
      ~ .brother {} /* ❌ Invalid — combinator requires & */
      & ~ .brother {} /* .parent > .child.sister ~ .brother */
      & + .sister {} /* .parent > .child.sister + .sister */
      + .sister {} /* ❌ Invalid — combinator requires & */
    }
    & > .grandchild {} /* .parent > .child > .grandchild */
    .grandchild {} /* .parent > .child .grandchild */
  }
}

field-sizing

input,
textarea,
select {
  padding:0.5em 1em;
  min-width: 7em;
  max-width:100%;
}

Let's improve this with 1 line!

input,
textarea,
select {
  padding:0.5em 1em;
  min-width: 7em;
  max-width:100%;
  field-sizing: content;
}

container queries

HTML

...
...
...
...
...

CSS

.grid {
  display: grid;
  gap: var(--gap);

  &[columns="3"] {
    grid-template-columns: repeat(3, 1fr);
  }
  &[columns="2"] {
    grid-template-columns: repeat(2, 1fr);
  }
}

Responsive CSS w/ @media queries

@media (max-width: 940px) {
  .grid[columns="3"] {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (max-width: 700px) {
  .grid[columns="2"] {
    grid-template-columns: repeat(1, 1fr);
  }
}
@media (max-width: 620px) {
  .grid[columns="3"] {
    grid-template-columns: repeat(1, 1fr);
  }
}

The advertisers would like a word with you.

@media queries

@media (max-width: 940px) {
  .grid[columns="3"] {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (max-width: 700px) {
  .grid[columns="2"] {
    grid-template-columns: repeat(1, 1fr);
  }
}
@media (max-width: 620px) {
  .grid[columns="3"] {
    grid-template-columns: repeat(1, 1fr);
  }
}

@container queries

.content {
  container-type: inline-size;
  .grid[columns="2"] {
    @container (max-width: 560px) {
      grid-template-columns: 1fr;
    }
  }
  .grid[columns="3"] {
    @container (max-width: 600px) {
      grid-template-columns: repeat(2, 1fr);
    }
    @container (max-width: 400px) {
      grid-template-columns: repeat(1, 1fr);
    }
  }
}

.ads {
  container-type: inline-size;
  .grid[columns="2"] {
    @container (max-width: 340px) {
      grid-template-columns: 1fr;
    }
  }
}

let's make this fancier...

.content {
  container-type: inline-size;
  .grid[columns="2"] {
    @container (max-width: 560px) {
      grid-template-columns: 1fr;
    }
  }
  .grid[columns="3"] {
    @container (max-width: 600px) {
      grid-template-columns: repeat(2, 1fr);
    }
    @container (max-width: 400px) {
      grid-template-columns: repeat(1, 1fr);
    }
  }
}

.ads {
  container-type: inline-size;
  .grid[columns="2"] {
    @container (max-width: 340px) {
      grid-template-columns: 1fr;
    }
  }
}

...with named @container queries

.content {
  container-type: inline-size;
  container-name: content-container;
  .grid[columns="2"] {
    @container content-container (max-width: 560px) {
      grid-template-columns: 1fr;
    }
  }
  .grid[columns="3"] {
    @container content-container (max-width: 600px) {
      grid-template-columns: repeat(2, 1fr);
    }
    @container content-container (max-width: 400px) {
      grid-template-columns: repeat(1, 1fr);
    }
  }
}

.ads {
  container-type: inline-size;
  container-name: stupid-ads;
  .grid[columns="2"] {
    @container stupid-ads (max-width: 340px) {
      grid-template-columns: 1fr;
    }
  }
}

Animate to height:auto

Let's build an accordion!

HTML
...
Question #2
...
Question #2
...
Javascript
document.addEventListener("click", (event) => {
  if (event.target.matches("button[aria-controls]")) {
    const button = event.target;
    const content = button.closest("dt").nextElementSibling;

    // Remove 'active' class and set aria-expanded to false for all buttons
    document.querySelectorAll("button[aria-controls]").forEach((btn) => {
      if (btn !== button) {
        btn.classList.remove("active");
        btn.setAttribute("aria-expanded", "false");
      }
    });

    // Remove 'visible' class from all content elements
    document.querySelectorAll('[role="region"]').forEach((item) => {
      if (item !== content) {
        item.classList.remove("visible");
      }
    });

    // Toggle the clicked button and its corresponding content
    const isActive = button.classList.toggle("active");
    button.setAttribute("aria-expanded", isActive ? "true" : "false");
    if (content && content.matches('[role="region"]')) {
      content.classList.toggle("visible");
    }
  }
});

HTML w/ JS
...
...
...
HTML w/ JS
...
...
...
CSS, attempt #1
dl {
  dd { 
    overflow:hidden;
  }
  &[example-number="1"] {
    dd {
      height: 0;
      &.visible {
        height: auto;
      }
    }
  }
}
Let's animate this!
dl {
  dd { 
    overflow:hidden;
    height: 0;
    &.visible {
      height: auto;
    }
  }
}
simple enough, right?
dl {
  dd { 
    overflow:hidden;
    height: 0;
    transition: height 2s ease-out;
    &.visible {
      height: auto;
    }
  }
}
:(
dl {
  dd { 
    overflow:hidden;
    height: 0;
    transition: height 2s ease-out;
    &.visible {
      height: auto;
    }
  }
}
dl {
  dd { 
    overflow:hidden;
    height: 0;
    transition: height 2s ease-out;
    &.visible {
      height: 300px;
    }
  }
}

CSS Grid to the rescue!!!

dl {
  dd { 
    display:grid; 
    grid-template-rows: 1fr
  }
}
dl {
  dd { 
    display:grid; 
    grid-template-rows: 1fr;
  }
}
1fr = 1 fraction
/* All of these are valid */
dd {
  grid-template-columns: 1fr;
  grid-template-rows: 1fr 1fr;
  grid-template-rows: repeat(4,1fr);
  grid-template-columns: 20% 1fr;
  grid-template-rows: 210px 1fr 2rem;
}
/* All of these are valid */
dd {
  grid-template-columns: 1fr;
  grid-template-rows: 1fr 1fr;
  grid-template-rows: repeat(4,1fr);
  grid-template-columns: 20% 1fr;
  grid-template-rows: 210px 1fr 2rem;
  grid-template-colummns: 0fr;
  grid-template-rows: 0fr;
  /* 👆 yes, those too! */
  /* and you can animate any of these! */
}
dl {
  dd { 
    display:grid; 
    grid-template-rows: 0fr;
  }
}
dl {
  dd { 
    display:grid; 
    grid-template-rows: 0fr;
    transition: grid-template-rows 2s ease-out;
    &.visible {
      grid-template-rows: 1fr;
    }
  }
}
/* Okay, we also have to do this... */
dl {
  dd { 
    display:grid; 
    grid-template-rows: 0fr;
    transition: grid-template-rows 2s ease-out;
    .inside {
      visibility: hidden;
    }
    &.visible {
      grid-template-rows: 1fr;
      .inside {
        visibility: hidden;
      }
    }
  }
}

but now...

There's calc-size()

Let's go back to our first example
dl {
  dd { 
    height: 0;
    transition: height 2s ease-out;
    &.visible {
      height: auto;
    }
  }
}
One tiny change...
dl {
  dd { 
    height: 0;
    transition: height 2s ease-out;
    &.visible {
      height: calc-size(auto, size);
    }
  }
}

Works on width too!

@starting-style

HTML
...
CSS
.container {
  position: relative;
}
.hamburger-menu {
  position: absolute;
  top: var(--topnav-height);
  right: 0;
  width: 100%;
  max-width: var(--maxburger);
  background-color: $black;
  /* etc */
}
Javascript
const hamburgerMenu = document.querySelector("#HamburgerMenu");
const toggleButton = document.querySelector("#ToggleHamburgerMenu");

toggleButton.addEventListener("click", () => {
  const isExpanded = toggleButton.getAttribute("aria-expanded") === "true";
  toggleButton.setAttribute("aria-expanded", !isExpanded);
  hamburgerMenu.classList.toggle("visible");
});
Hamburger menu is hidden
...
Hamburger menu is visible
...
CSS
.hamburger-menu {
  position: absolute;
  top: var(--topnav-height);
  right: 0;
  width: 100%;
  max-width: var(--maxburger);
  background-color: $black;
}
CSS w/ transition
.hamburger-menu {
  position: absolute;
  top: var(--topnav-height);
  right: 0;
  width: 100%;
  max-width: var(--maxburger);
  background-color: $black;
  transform: translateX(100%);
  transition: transform 2s ease-out;
  &.visible {
    transform: translateX(0)
  }
}

but now...

There's @starting-style

CSS w/ transition
.hamburger-menu {
  position: absolute;
  top: var(--topnav-height);
  right: 0;
  width: 100%;
  max-width: var(--maxburger);
  background-color: $black;
  transform: translateX(100%);
  transition: transform 2s ease-out;
  &.visible {
    transform: translateX(0)
  }
}
CSS w/ @starting-style
.hamburger-menu {
  position: absolute;
  top: var(--topnav-height);
  right: 0;
  width: 100%;
  max-width: var(--maxburger);
  background-color: $black;
  transform: translateX(100%);
  transition: transform 2s ease-out;
  &.visible {
    transform: translateX(0)
  }
  @starting-style {
    transform: translateX(100%);
  }
}

animation-timeline: view()

HTML
  • An image of something
  • An image of something else
  • A third image
  • You get it
  • ...
ul {
  display: grid;
  grid-template-columns: repeat(2,1fr);
  gap: 0 var(--vertical-gap);
}
li { 
  list-style:none;
  &:nth-child (even) {
    margin-top: var(--horizontal-gap);
  }
  &:nth-child (odd) {
    margin-top: var(--negative-horizontal-gap);
  }
}

The design team has some notes.

Designer

‼️ We need this to animate!

Developer

Okay, sure.

How would you like it to look?

Designer

Make it look friggin' COOL!!! ✨😎💎💯🔥🤯🤩🎸⚡️🌀

CSS (Before)
ul {
  display: grid;
  grid-template-columns: repeat(2,1fr);
  gap: 0 var(--vertical-gap);
}
li { 
  list-style:none;
  &:nth-child (even) {
    margin-top: var(--horizontal-gap);
  }
  &:nth-child (odd) {
    margin-top: var(--negative-horizontal-gap);
  }
}
CSS (After)
ul {
  display: grid;
  grid-template-columns: repeat(2,1fr);
  gap: 0 var(--vertical-gap);
}
li { 
  list-style:none;
  &:nth-child (even) {
    margin-top: var(--horizontal-gap);
  }
  &:nth-child (odd) {
    margin-top: var(--negative-horizontal-gap);
  }
}
@keyframes appear {
  from {
    opacity: 0;
    scale: 0.25;
    transform: translateY(100%);
  }
  to {
    opacity: 1;
    scale: 1;
    transform: translateY(0);
  }
}
img {
  animation: appear linear;
  animation-timeline: view();
  animation-range: entry 0 cover 35%;
}

Let's see what the designer thinks...

Designer

OMG YES! MORE! 🤸‍♀️🥳🎊✨🎉🍾🥂🕺💃🌟🔥

Developer

Uhhhhhhhhh.... Okay, sure.

CSS (Before)
@keyframes appear {
  from {
    opacity: 0;
    scale: 0.25;
    transform: translateY(100%);
  }
  to {
    opacity: 1;
    scale: 1;
    transform: translateY(0);
  }
}
img {
  animation: appear linear;
  animation-timeline: view();
  animation-range: entry 0 cover 35%;
}
CSS (After)
@keyframes appear {
  from {
    opacity: 0;
    scale: 0.25;
    transform: translateY(100%);
  }
  to {
    opacity: 1;
    scale: 1;
    transform: translateY(0);
  }
}
@keyframes disappear {
  from {
    opacity: 1;
    scale: 1;
    transform: translateY(0);
  }
  to {
    opacity: 0;
    scale: 0.25;
    transform: translateY(-100%);
  }
}
img {
  animation: appear linear;
  animation-timeline: view();
  animation-range: entry 0 cover 45%;
}
li:has(img) {
  animation: disappear linear forwards;
  animation-timeline: view();
  animation-range: exit 0 exit 45%;
}

So... That's a lot...

CSS (Before)
img {
  animation: appear linear;
  animation-timeline: view();
  animation-range: entry 0 cover 45%;
}
li:has(img) {
  animation: disappear linear forwards;
  animation-timeline: view();
  animation-range: exit 0 exit 45%;
}
CSS (After)
@media (prefers-reduced-motion: no-preference) {
  img {
    animation: appear linear;
    animation-timeline: view();
    animation-range: entry 0 cover 45%;
  }
  li:has(img) {
    animation: disappear linear forwards;
    animation-timeline: view();
    animation-range: exit 0 exit 45%;
  }
}

:has()!

My favorite CSS QoL improvement since grid

Select any descendent

.target-person .person { }

Select direct descendent

.target-person > .person { }

Select next sibling

.target-person + .person { }

Select siblings *

.target-person ~ .person { }

* assuming that sibling comes later in the order

.target-person  ~ .person { }

But we can't do this...

.target-person < .person { } 
 

But now there's has()!

So, let's make some cards...

{{Product Title}}

{{product.description}}
{{product.image}}

{{Product Title}}

{{product.description}}
And let's style them differently...
.card-conainer {
  display: flex;
  flex-direction: column;
  background: var(--no-image-bg);
}
.card-container .description {
  display:flex;
  flex-grow:1;
  justify-content: center;
  align-items: center;
}
.card-container:has(img) {
  border:  var(--has-image-border);
  box-shadow: var(--has-image-shadow);
  background-color: var(--has-image-bg);
}
Okay, let's build a form!
And let's style it with :has() !!!

          
fieldset:has([required]) {
  label {
    &:after {
      content: "*";
      color: var(--red);
    }
  }
}
fieldset:has(:focus) {
  label { color:var(--blue); }
  input, 
  textarea,
  select {
    border-color: var(--blue);
    color:  var(--blue);
  }
}
fieldset:has(input[required]:invalid) {
  border-color: var(--red);
  label {
    color: var(--red);
  }
}
fieldset:has(select[required]:invalid) {
  border-color: var(--red);
  label {
    color: var(--red);
  }
}
Hooray! Another grid!!!
...
...
...
.grid[columns="3"] {
  display:grid;
  gap: var(---gap);
  grid-template:repeat(3,1fr)
}
Designer

IS THAT A WIDOW!!!????????

.grid[columns="3"] {
  display:grid;
  gap: var(---gap);
  grid-template:repeat(3,1fr)
}

/* Option 1... */
.grid:has(.box:nth-child(4)) {
  grid-template-columns: repeat(2,1fr)
}
.grid[columns="3"] {
  display:grid;
  gap: var(---gap);
  grid-template:repeat(3,1fr)
}

/* Option 2... */
.grid:has(.box:nth-child(4)) {
  display:flex;
  flex-wrap: wrap;
  justify-content: center;
}
.grid:has(.box:nth-child(4)) .box {
  flex-basis:33.333%;
  max-width: calc(33.333% - var(--halfGap));
}

:not()

We'll make this quick..

Some examples:
a:not([target="_blank"]) {
  /*
    Do something to any links that don't open in a new tab.
  */
}

input:not([disabled]):not([readonly]) {
  /*
    Do something to inputs which are editable,
    meaning they're not marked as disabled or readonly.
  */
}

.content p:not(:first-of-type) {
  /*
    Do something to all paragraphs after the 1st one.
  */
}

.topnav li a:not([aria-current="page"]) {
  /*
    Add some link styles to any link that doesn't go to this page.
  */
}

.card:not(.featured) a {
  /*
    Do something to any link in non-featured cards.
  */
}

text-wrap

.default {
  /* text-wrap:wrap is the default */
}
.pretty {
  text-wrap: pretty;
}
.balanced {
  text-wrap: balanced;
}

Speed Round!

  1. @layer
  2. dark-light()
  3. @property
  4. scrollbar
  5. ::first-letter
@layer
@layer red {
  p { color: red; }
}

@layer green {
  p { color: green; }
}

@layer blue {
  p { color: blue; }
}

@layer blue, green, red;

/*
  RESULT: p { color: red; }
*/
dark-light()
:root {
  --text-color: dark-light(black, white)
  --bg-color: dark-light(white, black);
  --accent-color: dark-light(purple, lightblue);
}

body {
  color: var(--text-color);
  background-color: var(--bg-color);
}

button {
  background-color: var(--accent-color);
  color: var(--bg-color);
}
property()
@property --color-start {
  syntax: '<color>';
  initial-value: blue;
}

@property --size-multiplier {
  syntax: '<number>';
  initial-value: 1;
}

.my-element {
  background-color: var(--color-start);
  width: calc(100px * var(--size-multiplier));
}
Styling a scrollbar...
.scrollable {
  scrollbar-width: thin;
  scrollbar-color: gold silver; /* Gold thumb, silver track */
}
Styling a scrollbar...
.scrollable {
  scrollbar-width: thin;
  scrollbar-color: gold silver; /* Gold thumb, silver track */
}

/*
  Okay, you're gonna need a bunch of vender-prefix crap...
*/

.scrollable {
  &::-webkit-scrollbar {
    width: 10px;
    height: 10px;
    background-color: transparent;
  }

  &::-webkit-scrollbar-track {
    /* The main background of the scrollbar area */
    background: silver; 
    border-radius: 5px;
  }

  &::-webkit-scrollbar-thumb {
    /* The draggable part of the scrollbar */
    background-color: gold; 
    border-radius: 5px;
    border: 2px solid silver;
  }

  &::-webkit-scrollbar-thumb:hover {
    background-color: darkgoldenrod;
  }

  &::-webkit-scrollbar-corner {
    /* Keeping a light gray for the corner */
    background-color: lightgray;
  }
}
::first-letter
p {
  color: var(--color-normal);
  font-family: var(--font-normal);

  &:first-letter {
    initial-letter: 4 3; /* height width */
    color: var(--color-silly);
    font-family: var(--font-silly);
  }
}
CodePen example

Takeaways...

chrome.dev/css-wrapped-2024
developer.chrome.com/blog/css-wrapped-2023
codepen.io
css-tricks.com

This Talk!

ahoylemon.github.io/new-css
Lea Verou
Font Awesome
Jen Simmons
Apple
Miriam Suzanne
OddBird
Kevin Powell
YouTube video maker
Sara Soueidan
educator
Una Kravets
Google
Lemon
Savas Labs

🤙🏻 hit me up

Training, mentoring, advice, etc
Mail.Ru lemon@ahoylemon.xyz
Bluesky @ahoylemon.xyz
Mastodon @ahoylemon@mastodon.social
Sessionize sessionize.com/lemon
GitHub github.com/AhoyLemon

🤙🏻 hit me up

Find me, I'm around.

Thank you.

ahoylemon.xyz