Dropdowns

Dropdown styles for <details> disclosure element content with light-dismiss functionality.

Examples (anchor)

Please note. The dropdowns will inherit whatever normalize/reset styles are present requiring only minor adjustments to suit base font property values, but as demonstrated they've been designed to work seamlessly with the StyleMods content styles.

Default (anchor)

The dropdowns are standard HTML <details> disclosure elements that are automatically restyled into dropdown components when content within the element is wrapped within one of the three container options shown below.

Default
List type
Body mixed
Example HTML

The name attribute allows only one to be opened at the same time, the .close-dropdown utility enables clicking outside the dropdown to close it and requires the dropdown.js provided with the source files and shared below. Where links are used buttons can also be included if required.

<details name="dropdown-demo">
<summary>Default</summary>
<div class="dropdown">
  <a href="#">Action</a>
  <a href="#">Another action</a>
  <a href="#">Longer action text</a>
</div>
<div class="close-dropdown"></div>
</details>

<details name="dropdown-demo">
<summary>List type</summary>
<ul class="dropdown-list">
  <li><a href="#">Action</a></li>
  <li><a href="#">Another action</a></li>
  <li><a href="#">Longer action text</a></li>
</ul>
<div class="close-dropdown"></div>
</details>

<details name="dropdown-demo">
<summary>Body mixed</summary>
<div class="dropdown-body">
  <h3>With heading</h3>
  <p>The quick brown fox jumps over the lazy dog.</p>
  <button>With button</button>
</div>
<div class="close-dropdown"></div>
</details>

The <summary> element is lightly styled so can be designed to suit the specific conditions in which the dropdowns are being used. The icon styles replicate those from the icons and can be adapted to use the SVG variable tokens if also included.

The .dropdown-btn utility can be used on the <summary> to convert it into a button designed to replicate the forms and buttons content styles and button component utilities.

Default
List type
Body mixed
Example HTML
<details name="dropdown-demo">
<summary class="dropdown-btn">Default</summary>
<div class="dropdown">
  <a href="#">Action</a>
  <a href="#">Another action</a>
  <a href="#">Longer action text</a>
</div>
<div class="close-dropdown"></div>
</details>

<details name="dropdown-demo">
<summary class="dropdown-btn">List type</summary>
<ul class="dropdown-list">
  <li><a href="#">Action</a></li>
  <li><a href="#">Another action</a></li>
  <li><a href="#">Longer action text</a></li>
</ul>
<div class="close-dropdown"></div>
</details>

<details name="dropdown-demo">
<summary class="dropdown-btn">Body mixed</summary>
<div class="dropdown-body">
  <h3>With heading</h3>
  <p>The quick brown fox jumps over the lazy dog.</p>
  <button>With button</button>
</div>
<div class="close-dropdown"></div>
</details>

Fixed light and dark (anchor)

Default light
Default dark
Example HTML
<details name="dropdown-demo">
<summary>Default light</summary>
<div class="dropdown dropdown-light">
  <a href="#">Action</a>
  <a href="#">Another action</a>
  <a href="#">Longer action text</a>
</div>
<div class="close-dropdown"></div>
</details>

<details name="dropdown-demo">
<summary>Default dark</summary>
<div class="dropdown dropdown-dark">
  <a href="#">Action</a>
  <a href="#">Another action</a>
  <a href="#">Longer action text</a>
</div>
<div class="close-dropdown"></div>
</details>
Example HTML
<details name="dropdown-demo" class="dropdown-light">
<summary class="dropdown-btn">Default light</summary>
<div class="dropdown">
  <a href="#">Action</a>
  <a href="#">Another action</a>
  <a href="#">Longer action text</a>
</div>
<div class="close-dropdown"></div>
</details>

<details name="dropdown-demo" class="dropdown-dark">
<summary class="dropdown-btn">Default dark</summary>
<div class="dropdown">
  <a href="#">Action</a>
  <a href="#">Another action</a>
  <a href="#">Longer action text</a>
</div>
<div class="close-dropdown"></div>
</details>

Using the module (anchor)

To use the module load the StyleMods scss directory as follows (changing the path to suit the source files location as required) then include the Sass mixin anywhere below.

custom.scss
@use "stylemods/scss" as *;
@include dropdowns-css;

Source code (anchor)

See customizing for information about using the Sass and CSS variables in the source code to customize the styles, and Sass functionality (on the using StyleMods page) for other ways to use the variables to create custom styles.

dropdowns.scss
// ---------------------------------------------------------- 
// Dropdowns
// ----------------------------------------------------------
$dropdown-text-color:                     var(--dropdown-text, CanvasText) !default;
$dropdown-background-color:               var(--dropdown-bg, Canvas) !default;
$dropdown-border-color:                   var(--dropdown-bd-color, color-mix(in srgb, CanvasText 22%, Canvas)) !default;
$dropdown-link-button-background-color:   var(--dropdown-link-bg, transparent) !default;
$dropdown-link-button-background-hover:   var(--dropdown-link-hover, color-mix(in srgb, CanvasText 4%, Canvas)) !default;
$dropdown-inline-size:                    var(--dropdown-min, fit-content) !default;
$dropdown-max-inline-size:                var(--dropdown-max, 14rem) !default;
$dropdown-margin-block-start:             var(--dropdown-top, 0.25rem) !default;
$dropdown-padding-block:                  var(--dropdown-py, 0.313rem) !default;
$dropdown-padding-inline:                 var(--dropdown-px, 0.9rem) !default;
$dropdown-body-padding-block:             var(--dropdown-body-py, 0.75rem) !default;
$dropdown-content-margin-block-end:       var(--dropdown-content-mb, 0.75rem) !default;
$dropdown-radius:                         var(--dropdown-radius, 0.188rem) !default;

// Button
$dropdown-button-text-color:              var(--dropdown-btn-text, CanvasText) !default;
$dropdown-button-background-color:        var(--dropdown-btn-bg, color-mix(in srgb, CanvasText 4%, Canvas)) !default;
$dropdown-button-background-hover:        var(--dropdown-btn-hover, color-mix(in srgb, CanvasText 8%, Canvas)) !default;
$dropdown-button-focus-color:             var(--dropdown-focus-color, color-mix(in srgb, CanvasText 22%, Canvas)) !default;
$dropdown-button-padding-block:           var(--dropdown-btn-py, 0.313rem) !default;
$dropdown-button-focus-shadow:            0 0 0 0.06rem !default;

// Icons
$dropdown-icon-color:                     var(--dropdown-ico, CanvasText) !default;
$dropdown-open-icon:                      url("data:image/svg+xml,<svg viewBox='0 0 16 16' fill='currentColor' xmlns='http://www.w3.org/2000/svg'><path d='m8 10-4-4h8z'/></svg>") !default;
$dropdown-close-icon:                     url("data:image/svg+xml,<svg viewBox='0 0 16 16' fill='currentColor' xmlns='http://www.w3.org/2000/svg'><path d='m8 6 4 4-8 7e-7z'/></svg>") !default;

@mixin dropdowns-css {

:where(details:has(.dropdown, .dropdown-list, .dropdown-body)) {
  margin-block-end: 0;
}

details:has(.dropdown, .dropdown-list, .dropdown-body) summary {
  list-style-type: "";
  position: relative;
  display: inline-block;
  z-index: 1;
}

details:has(.dropdown, .dropdown-list, .dropdown-body) summary::-webkit-details-marker { 
  display: none;
}

details[open]:has(.dropdown, .dropdown-list, .dropdown-body) summary {
  margin-block-end: 0;
}

details:has(.dropdown, .dropdown-list, .dropdown-body) summary:after {
  --svg: #{$dropdown-open-icon};
  display: inline-block;
  content: "";
  block-size: 1em;
  inline-size: 1em;
  vertical-align: -.115em;
  background-color: $dropdown-icon-color;
  mask-image: var(--svg);
  mask-repeat: no-repeat;
  margin-inline-start: 0.2rem;
}

details[open]:has(.dropdown, .dropdown-list, .dropdown-body) summary:after {
  --svg: #{$dropdown-close-icon};
}

.dropdown, .dropdown-list, .dropdown-body {
  color: $dropdown-text-color;
  position: absolute;
  inline-size: $dropdown-inline-size;
  max-inline-size: $dropdown-max-inline-size;
  margin-block-start: $dropdown-margin-block-start;
  border: 1px solid $dropdown-border-color;
  border-radius: $dropdown-radius;
  background-color: $dropdown-background-color;
  z-index: 2;
}

.dropdown {
  display: grid;
}

.dropdown-list {
  list-style-type: "";
  padding-inline-start: 0;
}

:is(.dropdown, .dropdown-list) :is(a, button) {
  padding-block: $dropdown-padding-block;
  padding-inline: $dropdown-padding-inline;
  border: none;
  border-radius: 0;
  background-color: $dropdown-link-button-background-color;
}

:is(.dropdown, .dropdown-list) a {
  display: block;
  text-decoration: none;
}

:is(.dropdown, .dropdown-list) button {
  color: $dropdown-text-color;
}

.dropdown-list button {
  inline-size: 100%;
}

:is(.dropdown, .dropdown-list) :is(a, button):is(:hover, :focus) {
  background-color: $dropdown-link-button-background-hover;
}

:is(.dropdown, .dropdown-list) :is(a, button):focus {
  outline: none;
  box-shadow: none;
}

.dropdown :is(a, button):not(:last-child),
.dropdown-list li:not(:last-child) {  
  border-block-end: 1px solid $dropdown-border-color;
}

.dropdown :first-child:is(a, button),
.dropdown-list li:first-child *:is(a, button) {
  border-start-start-radius: calc($dropdown-radius - 1px);
  border-start-end-radius: calc($dropdown-radius - 1px);
}

.dropdown :last-child:is(a, button),
.dropdown-list li:last-child *:is(a, button) {
  border-end-start-radius: calc($dropdown-radius - 1px);
  border-end-end-radius: calc($dropdown-radius - 1px);
}

.dropdown-body {
  padding-block: $dropdown-body-padding-block;
  padding-inline: $dropdown-padding-inline;
}

:where(.dropdown-body :is(h1, h2, h3, h4, h5, h6, p, ol, ul, dl, figure, address)) {
  margin-block-end: $dropdown-content-margin-block-end;
}

:where(.dropdown-body *:last-child) {
  margin-block-end: calc($dropdown-content-margin-block-end * 0.33);
}

// Dropdown button
.dropdown-btn {
  color: $dropdown-button-text-color;
  padding-block: $dropdown-button-padding-block;
  padding-inline-start: $dropdown-padding-inline;
  padding-inline-end: calc($dropdown-padding-inline * 0.75); // adjusted for icon's inline space
  border: 1px solid $dropdown-border-color;
  border-radius: $dropdown-radius;
  background-color: $dropdown-button-background-color;
}

.dropdown-btn:is(:hover, :focus-visible) {
  background-color: $dropdown-button-background-hover;
}

.dropdown-btn:focus-visible {
  outline: none;
  box-shadow: $dropdown-button-focus-shadow $dropdown-button-focus-color;
}

// Light-dismiss close dropdown backdrop
.close-dropdown {
  position: fixed;
  inset: 0;
  display: block;
  content: "";
}

// Color-scheme
.dropdown-light {
  color-scheme: light;
}

.dropdown-dark {
  color-scheme: dark;
}

} // end dropdowns-css
dropdown.js
//  ------------------------------------------------------------
//  Adapted from Mark Otto's https://codepen.io/emdeoh/pen/QWOVPVK
//  ------------------------------------------------------------
let details = document.querySelectorAll("details");
details && details.forEach(function (e) {
  let t;
  [e.querySelector(".close-dropdown")].forEach(function (e) {
    console.log("toggle: " + e), e && e.addEventListener("click", t => {
      e.closest("details[open]")
        .removeAttribute("open")
    })
  })
});

The dropdown.js works with the .close-dropdown utility in the examples to provide the ability to click on the page background to close the dropdown, it's been adapted from the details element section of Mark Otto's Fun with the dialog element article which also provides the original script adapted for the StyleMods dialogs.

The script is only needed if this functionality is required, the dropdowns will still look the same and open and close using the native <details> disclosure behaviour without it.