Skip to main content

Disclosure v2.4.0

Reaveal or hide additional content when triggered, helping to manage the display of supplementary information.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam congue, urna eu efficitur accumsan, massa sem rhoncus elit, eu mattis erat metus gravida dolor. Maecenas imperdiet sodales dolor, sit amet malesuada lorem tincidunt in. Nunc a risus augue. Sed pellentesque pharetra iaculis. Donec gravida risus ut placerat venenatis. Integer sed erat eget risus rutrum condimentum. Curabitur tincidunt nisl ornare, viverra est eget, condimentum lectus. Integer pretium justo ac odio vestibulum ornare. Nullam mi risus, faucibus lacinia justo et, auctor vehicula lectus.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam congue, urna eu efficitur accumsan, massa sem rhoncus elit, eu mattis erat metus gravida dolor. Maecenas imperdiet sodales dolor, sit amet malesuada lorem tincidunt in. Nunc a risus augue. Sed pellentesque pharetra iaculis. Donec gravida risus ut placerat venenatis. Integer sed erat eget risus rutrum condimentum. Curabitur tincidunt nisl ornare, viverra est eget, condimentum lectus. Integer pretium justo ac odio vestibulum ornare. Nullam mi risus, faucibus lacinia justo et, auctor vehicula lectus.

Reminder: A disclosure is intended for (mostly) single-purpose use. If you need to group multiple disclosures, such as in a FAQ or similar layout, please use the Accordion component.

<button class="disclosure--btn">Learn More</button>

<div class="disclosure--content">

  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam congue, urna eu efficitur accumsan, massa sem rhoncus elit, eu mattis erat metus gravida dolor. Maecenas imperdiet sodales dolor, sit amet malesuada lorem tincidunt in. Nunc a risus augue. Sed pellentesque pharetra iaculis. Donec gravida risus ut placerat venenatis. Integer sed erat eget risus rutrum condimentum. Curabitur tincidunt nisl ornare, viverra est eget, condimentum lectus. Integer pretium justo ac odio vestibulum ornare. Nullam mi risus, faucibus lacinia justo et, auctor vehicula lectus.</p>

  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam congue, urna eu efficitur accumsan, massa sem rhoncus elit, eu mattis erat metus gravida dolor. Maecenas imperdiet sodales dolor, sit amet malesuada lorem tincidunt in. Nunc a risus augue. Sed pellentesque pharetra iaculis. Donec gravida risus ut placerat venenatis. Integer sed erat eget risus rutrum condimentum. Curabitur tincidunt nisl ornare, viverra est eget, condimentum lectus. Integer pretium justo ac odio vestibulum ornare. Nullam mi risus, faucibus lacinia justo et, auctor vehicula lectus.</p>

</div>
/*!

  Radancy Component Library: Disclosure

  Contributor(s):
  Michael "Spell" Spellacy

  Dependencies: Sass

*/

// Sass Variables

$disclosure-breakpoint: 60em;

:root {

  --disclosure-arrow-size: 0.8em; // Toggle Arrow Size
  --disclosure-arrow-border: calc(2rem / 16); // Toggle Arrow Border
  --disclosure-base-color: #6F00EF; // Primary Color
  --disclosure-color-active: #a9a9a9; // Active State
  --disclosure-color-default: #d3d3d3; // Default States
  --disclosure-color-interactive: #666; // Interactive State 
  --disclosure-color-selected: var(--disclosure-base-color); // Selected States

}

// Disclosure Button

.disclosure--btn {
background-color: var(--disclosure-color-default) ;
border: 0;
color: inherit;
cursor: pointer;
display: grid;
font-size: inherit;
grid-auto-flow: column;
line-height: normal;
padding: 1em;
position: relative;
text-align: start;
width: 100%;

  // Accessibility: Focus and hover states must meet a 3:1 color ratio between background color and foreground text.
  // The project designer should be providing this state. Also, never remove focus outlines! Either redesign them or 
  // design an alternative focus state. Learn: https://webaim.org/articles/contrast/

  &:focus-visible, &:hover {
  background-color: var(--disclosure-color-active);
  }
    
  &:focus-visible {
  outline-offset: -5px;
  }

  &[aria-expanded="true"] {
  background-color: var(--disclosure-color-selected);
  color: #fff;

    // Disclosure Arrow: State

    .disclosure--arrow {
    border-block-end-color: #fff;
    border-inline-start-color: #fff;
    transform: rotate(135deg);
    translate: 0 calc(8rem/16);
    }

  }

}

// Disclosure Heading

.disclosure--heading {
all: unset;
display: contents;
}

// Disclosure Arrow

.disclosure--arrow {
block-size: var(--disclosure-arrow-size);
border-width: var(--disclosure-arrow-border);
border-block-color: transparent var(--disclosure-color-selected);
border-block-start-width: 0;
border-inline-color: var(--disclosure-color-selected) transparent;
border-inline-end-width: 0;
border-style: solid;
content: "";
direction: ltr;
display: block;
inline-size: var(--disclosure-arrow-size);
justify-self: end;
transform: rotate(-45deg);
}

// Disclosure Content

.disclosure--content {
background-color: var(--disclosure-color-default);
display: none;
overflow: hidden;
padding: 1em;

  &[data-animation] {
  
    @media (prefers-reduced-motion: no-preference) {
  
      transition: display 0s, max-height 3s allow-discrete;
  
    }
  
  }

}

// Persistent State 

.disclosure--btn, .disclosure--heading {

  &[data-persistent] {

    @media(min-width: $disclosure-breakpoint) {

      display: none;

      + .disclosure--content {
      display: block;
      }

    }

  }

}

// Viewport Context 

.disclosure--btn, .disclosure--heading {

  &[data-context], &[data-disclosure-viewport] {

    @media(min-width: $disclosure-breakpoint) {

      padding: 0;
      pointer-events: none;
      visibility: hidden;

        .disclosure--btn {
        padding: inherit;
        pointer-events: inherit;
        visibility: inherit;
        }

        span:not([aria-hidden]) {
        color: var(--disclosure-base-color);
        font-weight: 700;
        visibility: visible;
        }

        // If icon present

        span[aria-hidden] {
        display: none;
        }

        + .disclosure--content {
          background-color: transparent;
          display: block;
          padding: 0;
          }

    }

  }

}

// Disclosure Event

.disclosure--btn[aria-expanded="true"] + .disclosure--content, .disclosure--heading:has(.disclosure--btn[aria-expanded="true"]) + .disclosure--content {
display: block;

  &[data-animation] {
  
    max-height: 5000px;

    @starting-style {
    
      max-height: 0;
    
    }

  }

}
/*!

  Radancy Component Library: Disclosure

  Contributor(s):
  Michael "Spell" Spellacy

*/

(() => {

  "use strict";

  // Display which Disclosure is in use via console:

  console.log("%cDisclosure%cv2.4.0", "background: #2d2d2d; color: #fff; padding: 6px 10px; border-radius: 16px 0 0 16px; font-weight: 600;" , "background: #6e00ee; color: #fff; padding: 6px 10px; border-radius: 0 16px 16px 0; font-weight: 600;");

  // Commonly used Classes, Data Attributes, States, Strings, etc.

  const disclosureButtonClass = ".disclosure--btn";
  const disclosureButtons = document.querySelectorAll(disclosureButtonClass);
  const disclosureArrowClassName = "disclosure--arrow";
  const disclosureHeadingClass = ".disclosure--heading";
  const disclosureDataDefaultOpen = "data-open";
  const disclosureDataEnableAnchor = "data-enable-anchor";
  const disclosureDataRemoveArrow = "data-remove-arrow";

  // Add Listener and other needed attributes to disclosure button

  disclosureButtons.forEach((button, index) => {

    const count = index + 1;

    button.setAttribute("aria-expanded", "false");

    let thisButtonID, thisContentID;

    if (button.hasAttribute("id")) {

      // Check for custom ID on button.

      thisButtonID = button.getAttribute("id");
      thisContentID = `${thisButtonID}-content`;

    } else {

      // Else, add dynamic ID.

      thisButtonID = `disclosure-btn-${count}`;
      thisContentID = `disclosure-content-${count}`;

    }

    // Set ID and aria-controls on button.

    button.setAttribute("id", thisButtonID);
    button.setAttribute("aria-controls", thisContentID);

    if (button.closest(disclosureHeadingClass)) {

      // If button contains heading...

      button.closest(disclosureHeadingClass).nextElementSibling.setAttribute("id", thisContentID);

    } else {

      button.nextElementSibling.setAttribute("id", thisContentID);

    }

    // If custom arrow not needed.

    if (!button.hasAttribute(disclosureDataRemoveArrow)) {

      const toggleState = document.createElement("span");
      toggleState.setAttribute("aria-hidden", "true");
      toggleState.classList.add(disclosureArrowClassName);
      button.append(toggleState);

    }

    button.addEventListener("click", () => {

      const isExpanded = button.getAttribute("aria-expanded") === "true";
      
      button.setAttribute("aria-expanded", isExpanded ? "false" : "true");

      if (button.hasAttribute(disclosureDataEnableAnchor)) {

        history.pushState(null, null, `#${button.id}`);

      }

    });

    // Open desired disclosure.

    if (button.hasAttribute(disclosureDataDefaultOpen)) {

      button.setAttribute("aria-expanded", "true");

    }

  });

  // Open selected disclosure via URL

  const url = document.location.href;
  const hash = url.split("#");
  const disclosureID = document.getElementById(hash[1]);

  if(disclosureID) {

    const disclosureTarget = document.querySelector(disclosureButtonClass + "#" + disclosureID.id);

    if (disclosureTarget) {

      disclosureTarget.setAttribute("aria-expanded", "true");
      disclosureTarget.focus();

    }

  }

})();

Properties Anchor (Properties)

Use the following properties to configure this component.

Attribute Applies To Note Description
data-animation .disclosure--content Boolean, Optional
Used to enable content to be animated when opened. Please note that the default animation leverages @starting-style at-rule.
data-context .disclosure--btn, .disclosure--heading Boolean, Optional
Used to allow the disclosure to become non-interactive on larger viewport sizes, while maintaining interactivity on mobile. The older attribute, data-disclosure-viewport has been depracted and should no longer be used.
data-enable-anchor .disclosure--btn Boolean, Optional
Used to enable the anchor that appears in the address bar when a disclosure is pressed. Including an anchor can be useful for tracking and sharing specific content.
data-open .disclosure--btn Boolean, Optional
Specifies that content should be open on page load. If not specified, no content will be opened.
data-persistent .disclosure--btn, .disclosure--heading Boolean, Optional
Specifies that the content should remain persistent and always open in larger viewports. Attribute must be included on both the button and content. Must not be used with .disclosure--heading.
data-remove-arrow .disclosure--btn Boolean, Optional
By default, all buttons include a span element for icons. If this is not desirable, you can remove it by adding this attribute and using your own inline or CSS icon. For custom inline icons, ensure they are hidden from assistive technologies by using aria-hidden="true".

Class Applies To Note Description
.disclosure--btn button Mandatory
Required on your disclosure button.
.disclosure--content div, section Mandatory
Required on your disclosure content.
.disclosure--heading h1, h2, h3, h4, h5, h6 Optional
Include this class on any heading you might have that wraps your button. A heading should always wrap a button and not the other way around.

Release Notes Anchor (Release Notes)

Keep up to date on major changes to this component.

Bug
January 31, 2025: Fixed issue with script breaking when URL contains a page fragment that is not used by the disclosure. Addressed some redundancy as well. Thanks, James!
Feature
January 29, 2025: Added data-persistent, a new attribute for .disclosure--btn, allowing triggers to be hidden on desktop but visible on mobile.
Behavior
January 29, 2025: Reinstated the viewport-friendly method, allowing triggers to be non-interactive on larger viewports. The attribute is now called data-context and should be used going forward. data-disclosure-viewport is deprecated and will be removed in a future release.
Note
April 24, 2025: Upgraded component to ES6. Thanks, Donte!

Report Issues

Find a bug? Want a new feature? Report it on JIRA. You're so awesome!