<header id="example-header--compact" class="cwf-header cwf-header--gold cwf-header--compact">
    <div class="cwf-header__container">
        <div class="cwf-header__title cwf-header__title--reverse">
            <h1 class="cwf-header__department"> <a class="cwf-header__link" href="/">
                    Example Site
                </a>
            </h1>
        </div>
        <div class="cwf-header__controls"> <button class="cwf-header__toggle" aria-controls="cwf-header__search" aria-keyshortcuts="/">
                <i class="fas fa-search cwf-header__icon"></i>
                <span class="cwf-header__text">Search</span>
            </button>
        </div>
        <div id="cwf-header__features" class="cwf-header__features">
            <button class="cwf-header__exit" aria-controls="cwf-header__features">
                Close
            </button>
            <form id="cwf-header__search" class="cwf-header__search" action="https://search.vcu.edu/s/search.html">
                <input type="hidden" name="collection" value="vcu-meta" /> <input type="hidden" name="clive" value="vcu-ts" />
                <input type="text" name="query" id="query" class="cwf-header__input" required />
                <button type="submit" class="cwf-header__submit" aria-label="Submit">
                    <i class="fas fa-search"></i>
                </button>
                <label for="query" class="cwf-header__label">
                    Search
                </label>
            </form>
        </div>
    </div>
</header>
<header id="{{ id ?? 'cwf-header' }}"
    class="cwf-header cwf-header--{{ theme }} {{
    compact
        ? 'cwf-header--compact'
    }}">
    <div class="cwf-header__container">
        <div class="cwf-header__title cwf-header__title--reverse">
            <h1 class="cwf-header__department">
                {%- if parent and parent.name and not parent.url %}
                    <a class="cwf-header__link cwf-header__link--multi-line"
                        href="{{ unit.url }}">
                        <span class="h2 cwf-header__parent">
                            {{ parent.name }}
                        </span>
                        <span>{{ unit.name }}</span>
                    </a>
                {% else %}
                    <a class="cwf-header__link" href="{{ unit.url }}">
                        {{ unit.name }}
                    </a>
                {% endif %}
            </h1>
            {%- if parent and parent.name and parent.url %}
                <h2 class="cwf-header__parent">
                    <a class="cwf-header__link" href="{{ parent.url }}">
                        {{ parent.name }}
                    </a>
                </h2>
            {% endif -%}
        </div>
        {%- if quick_links or search %}
            <div class="cwf-header__controls">
                {%- if quick_links %}
                    <button class="cwf-header__toggle"
                        aria-controls="cwf-header__nav"
                        aria-keyshortcuts=".">
                        <i class="fas fa-ellipsis-v cwf-header__icon"></i>
                        <span class="cwf-header__text">Links</span>
                    </button>
                {% endif %} {%- if search %}
                    <button class="cwf-header__toggle"
                        aria-controls="cwf-header__search"
                        aria-keyshortcuts="/">
                        <i class="fas fa-search cwf-header__icon"></i>
                        <span class="cwf-header__text">Search</span>
                    </button>
                {% endif %}
            </div>
            <div id="cwf-header__features" class="cwf-header__features">
                <button class="cwf-header__exit"
                    aria-controls="cwf-header__features">
                    Close
                </button>
                {%- if quick_links %}
                    <nav id="cwf-header__nav"
                        class="cwf-header__nav"
                        aria-label="Quick links">
                        {%- for quick_link in quick_links %}
                            <a href="{{ quick_link.url }}"
                                class="cwf-header__link">
                                {{ quick_link.name }}
                            </a>
                        {%- endfor %}
                    </nav>
                {%- endif %} {%- if search %}
                    <form id="cwf-header__search"
                        class="cwf-header__search"
                        action="https://search.vcu.edu/s/search.html">
                        <input type="hidden"
                            name="collection"
                            value="vcu-meta" />
                        {%- if search.collection %}
                            <input type="hidden"
                                name="clive"
                                value="{{ search.collection }}" />
                        {% endif -%} {%- if search.scope %}
                            <input type="hidden"
                                name="scope"
                                value="{{ search.scope }}" />
                        {% endif -%}
                        <input type="text"
                            name="query"
                            id="query"
                            class="cwf-header__input"
                            required />
                        <button type="submit"
                            class="cwf-header__submit"
                            aria-label="Submit">
                            <i class="fas fa-search"></i>
                        </button>
                        <label for="query" class="cwf-header__label">
                            Search
                        </label>
                    </form>
                {% endif %}
            </div>
        {% endif %}
    </div>
</header>
{
  "unit": {
    "name": "Example Site",
    "url": "/"
  },
  "compact": true,
  "theme": "gold",
  "search": {
    "collection": "vcu-ts",
    "scope": null
  },
  "id": "example-header--compact"
}
  • Content:
    // Header component styles
    
    @use "../../shared/animation";
    @use "../../shared/media";
    @use "../../shared/style";
    @use "../../shared/theme";
    @use "sass:map";
    
    // Selector prefix
    $prefix: "cwf" !default;
    
    .#{$prefix}-header {
        display: flex;
        justify-content: center;
        padding-top: 1rem;
        padding-bottom: 1rem;
        background-color: var(--cwf-header--background-color);
        font-family: theme.font--sans-serif();
    
        @include media.breakpoint {
            min-height: 8rem;
            padding-top: 2rem;
            padding-bottom: 2rem;
        }
    }
    
    // Header white colors
    $background-color--white: style.color("white") !default;
    $link__color--white: style.color("gray-darkest") !default;
    $link--interact__color--white: style.color("black") !default;
    $input__background-color--white: style.color("white-dark") !default;
    $input--focus__background-color--white: style.color("white") !default;
    $input__border-color--white: style.color("white-darkest") !default;
    $input--hover__border-color--white: style.color("gray-lightest") !default;
    $input--focus__border-color--white: style.color("gray-darkest") !default;
    $input__color--white: style.color("gray-darkest") !default;
    $submit__border-color--white: style.color("white-darkest") !default;
    $submit--hover__border-color--white: style.color("gray-lightest") !default;
    $submit--focus__border-color--white: style.color("gray-darkest") !default;
    $label__color--white: style.color("gray-lightest") !default;
    
    // Header gray colors
    $background-color--gray: style.color("white-dark") !default;
    $link__color--gray: style.color("gray-darkest") !default;
    $link--interact__color--gray: style.color("black") !default;
    $input__background-color--gray: style.color("white") !default;
    $input--focus__background-color--gray: style.color("white") !default;
    $input__border-color--gray: style.darken("white-darkest", 12.5%) !default;
    $input--hover__border-color--gray: style.darken("gray-lightest", 21%) !default;
    $input--focus__border-color--gray: style.color("gray-darkest") !default;
    $input__color--gray: style.color("gray-darkest") !default;
    $submit__border-color--gray: style.color("white-darkest") !default;
    $submit--hover__border-color--gray: style.color("gray-lightest") !default;
    $submit--focus__border-color--gray: style.color("gray-darkest") !default;
    $label__color--gray: style.color("gray-lightest") !default;
    
    // Header gold colors
    $background-color--gold: style.color("gold") !default;
    $link__color--gold: style.color("gray-darkest") !default;
    $link--interact__color--gold: style.color("black") !default;
    $input__background-color--gold: style.color("white") !default;
    $input--focus__background-color--gold: style.color("white") !default;
    $input__border-color--gold: style.darken("gold", 20.5%) !default;
    $input--hover__border-color--gold: style.darken("gold", 30.5%) !default;
    $input--focus__border-color--gold: style.color("gray-darkest") !default;
    $input__color--gold: style.color("gray-darkest") !default;
    $submit__border-color--gold: style.color("white-darkest") !default;
    $submit--hover__border-color--gold: style.color("gray-lightest") !default;
    $submit--focus__border-color--gold: style.color("gray-darkest") !default;
    $label__color--gold: style.color("gray-lightest") !default;
    
    // Header dark colors
    $background-color--dark: style.color("gray-dark") !default;
    $link__color--dark: style.color("white") !default;
    $link--interact__color--dark: style.color("white-darkest") !default;
    $input__background-color--dark: style.color("gray") !default;
    $input--focus__background-color--dark: style.color("gray-light") !default;
    $input__border-color--dark: style.color("gray-darkest") !default;
    $input--hover__border-color--dark: style.color("black") !default;
    $input--focus__border-color--dark: style.color("black") !default;
    $input__color--dark: style.color("white") !default;
    $submit__border-color--dark: style.color("gray-darkest") !default;
    $submit--hover__border-color--dark: style.color("black") !default;
    $submit--focus__border-color--dark: style.color("black") !default;
    $label__color--dark: style.color("white-darkest") !default;
    
    // Header themes
    $themes: (
        "white": (
            "background-color": $background-color--white,
            "link-color": $link__color--white,
            "link-hover-focus-color": $link--interact__color--white,
            "input-background-color": $input__background-color--white,
            "input-focus-background-color": $input--focus__background-color--white,
            "input-border-color": $input__border-color--white,
            "input-hover-border-color": $input--hover__border-color--white,
            "input-focus-border-color": $input--focus__border-color--white,
            "input-color": $input__color--white,
            "submit-border-color": $submit__border-color--white,
            "submit-hover-border-color": $submit--hover__border-color--white,
            "submit-focus-border-color": $submit--focus__border-color--white,
            "label-color": $label__color--white
        ),
        "gray": (
            "background-color": $background-color--gray,
            "link-color": $link__color--gray,
            "link-hover-focus-color": $link--interact__color--gray,
            "input-background-color": $input__background-color--gray,
            "input-focus-background-color": $input--focus__background-color--gray,
            "input-border-color": $input__border-color--gray,
            "input-hover-border-color": $input--hover__border-color--gray,
            "input-focus-border-color": $input--focus__border-color--gray,
            "input-color": $input__color--gray,
            "submit-border-color": $submit__border-color--gray,
            "submit-hover-border-color": $submit--hover__border-color--gray,
            "submit-focus-border-color": $submit--focus__border-color--gray,
            "label-color": $label__color--gray
        ),
        "gold": (
            "background-color": $background-color--gold,
            "link-color": $link__color--gold,
            "link-hover-focus-color": $link--interact__color--gold,
            "input-background-color": $input__background-color--gold,
            "input-focus-background-color": $input--focus__background-color--gold,
            "input-border-color": $input__border-color--gold,
            "input-hover-border-color": $input--hover__border-color--gold,
            "input-focus-border-color": $input--focus__border-color--gold,
            "input-color": $input__color--gold,
            "submit-border-color": $submit__border-color--gold,
            "submit-hover-border-color": $submit--hover__border-color--gold,
            "submit-focus-border-color": $submit--focus__border-color--gold,
            "label-color": $label__color--gold
        ),
        "dark": (
            "background-color": $background-color--dark,
            "link-color": $link__color--dark,
            "link-hover-focus-color": $link--interact__color--dark,
            "input-background-color": $input__background-color--dark,
            "input-focus-background-color": $input--focus__background-color--dark,
            "input-border-color": $input__border-color--dark,
            "input-hover-border-color": $input--hover__border-color--dark,
            "input-focus-border-color": $input--focus__border-color--dark,
            "input-color": $input__color--dark,
            "submit-border-color": $submit__border-color--dark,
            "submit-hover-border-color": $submit--hover__border-color--dark,
            "submit-focus-border-color": $submit--focus__border-color--dark,
            "label-color": $label__color--dark
        )
    );
    
    $theme--keys: "background-color", "link-color", "link-hover-focus-color",
        "input-background-color", "input-focus-background-color",
        "input-border-color", "input-hover-border-color", "input-focus-border-color",
        "input-color", "submit-border-color", "submit-hover-border-color",
        "submit-focus-border-color", "label-color", "theme";
    
    @function theme--validate($instructions) {
        @return map.keys($instructions) == $theme--keys;
    }
    
    @mixin theme($instructions) {
        @if theme--validate($instructions) {
            $theme: map.get($instructions, "theme");
            $background-color: map.get($instructions, "background-color");
            $link-color: map.get($instructions, "link-color");
            $link-hover-focus-color: map.get(
                $instructions,
                "link-hover-focus-color"
            );
            $input-background-color: map.get(
                $instructions,
                "input-background-color"
            );
            $input-focus-background-color: map.get(
                $instructions,
                "input-focus-background-color"
            );
            $input-border-color: map.get($instructions, "input-border-color");
            $input-hover-border-color: map.get(
                $instructions,
                "input-hover-border-color"
            );
            $input-focus-border-color: map.get(
                $instructions,
                "input-focus-border-color"
            );
            $input-color: map.get($instructions, "input-color");
            $submit-border-color: map.get($instructions, "submit-border-color");
            $submit-hover-border-color: map.get(
                $instructions,
                "submit-hover-border-color"
            );
            $submit-focus-border-color: map.get(
                $instructions,
                "submit-focus-border-color"
            );
            $label-color: map.get($instructions, "label-color");
            .#{$prefix}-header--#{$theme} {
                --cwf-header--background-color: #{$background-color};
                --cwf-header__link--color: #{$link-color};
                --cwf-header__link--hover-focus--color: #{$link-hover-focus-color};
                --cwf-header__input--background-color: #{$input-background-color};
                --cwf-header__input--focus--background-color: #{$input-focus-background-color};
                --cwf-header__input--border-color: #{$input-border-color};
                --cwf-header__input--hover--border-color: #{$input-hover-border-color};
                --cwf-header__input--focus--border-color: #{$input-focus-border-color};
                --cwf-header__input--color: #{$input-color};
                --cwf-header__submit--border-color: #{$submit-border-color};
                --cwf-header__submit--hover--border-color: #{$submit-hover-border-color};
                --cwf-header__submit--focus--border-color: #{$submit-focus-border-color};
                --cwf-header__label--color: #{$label-color};
            }
        } @else {
            @warn "Invalid header theme instructions provided!";
        }
    }
    
    @mixin theme--official($theme) {
        $colors: map.get($themes, $theme);
    
        $instructions: map.merge(
            $colors,
            (
                "theme": $theme
            )
        );
    
        @include theme($instructions);
    }
    
    @each $theme, $colors in $themes {
        @include theme--official($theme);
    }
    
    .#{$prefix}-header--compact {
        min-height: 4rem;
        padding-top: 1rem;
        padding-bottom: 1rem;
    }
    
    .#{$prefix}-header__container {
        display: flex;
        align-items: center;
        justify-content: center;
        padding-left: 1rem;
        padding-right: 1rem;
        @include theme.contain;
    
        @include media.breakpoint {
            justify-content: space-between;
        }
    }
    
    .#{$prefix}-header__title {
        display: flex;
        flex-direction: column;
        align-items: center;
    
        @include media.breakpoint {
            align-items: flex-start;
        }
    }
    
    .#{$prefix}-header__title--reverse {
        flex-direction: column-reverse;
    }
    
    .#{$prefix}-header__department {
        font-size: 1.1667rem;
        font-weight: 500;
    
        @include media.breakpoint {
            font-size: 2rem;
        }
    }
    
    .#{$prefix}-header--compact .#{$prefix}-header__department {
        @include media.breakpoint {
            font-size: 1.1667rem;
        }
    }
    
    .#{$prefix}-header__department,
    .#{$prefix}-header__parent {
        margin-top: 0;
        margin-bottom: 0;
        padding-top: 0;
    }
    
    .#{$prefix}-header__parent {
        margin-bottom: 0.5rem;
        font-size: 0.889rem;
        font-weight: 400;
    }
    
    .#{$prefix}-header__link {
        font-weight: 400;
        text-decoration: none;
        color: var(--cwf-header__link--color);
        @include animation.transition(color);
    
        &:hover,
        &:focus {
            text-decoration: underline;
            color: var(--cwf-header__link--hover-focus--color);
        }
    }
    
    .#{$prefix}-header__link--multi-line {
        display: flex;
        flex-direction: column;
    }
    
    .#{$prefix}-header__controls {
        position: absolute;
        top: 0;
        right: 0.25rem;
        display: flex;
        @include style.z-index("page");
    
        @include media.breakpoint {
            display: none;
        }
    }
    
    // Header colors
    $toggle__color: style.darken("gray-dark", 0.75%) !default; // #313131
    $toggle--active__background-color: style.opacity("black", 5%) !default;
    $toggle--active__color: style.color("black") !default;
    
    .#{$prefix}-header__toggle {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: space-evenly;
        width: 48px;
        height: 64px;
        padding: 0.5rem 0;
        border: none;
        background-color: transparent;
        font-size: 0.65rem;
        font-weight: 700;
        color: var(--cwf-header__toggle--color);
        @include animation.transition(background-color, color);
        --cwf-header__toggle--color: #{$toggle__color};
        --cwf-header__toggle--active--background-color: #{$toggle--active__background-color};
        --cwf-header__toggle--active--color: #{$toggle--active__color};
    
        &:hover,
        &:focus {
            background-color: var(--cwf-header__toggle--active--background-color);
            color: var(--cwf-header__toggle--active--color);
        }
    
        & .#{$prefix}-header__icon {
            font-size: 1.25rem;
        }
    }
    
    .#{$prefix}-header__features {
        display: none;
    
        @include media.breakpoint {
            display: block;
        }
    }
    
    .#{$prefix}-header__exit {
        display: none;
    }
    
    .#{$prefix}-header__features--nav-modal {
        align-items: flex-end;
    
        @include media.breakpoint {
            align-items: center;
        }
    }
    
    $features--modal__background-color: style.opacity("black", 75%) !default;
    $features--modal__background-color--reduced-transparency: style.color(
        "black"
    ) !default;
    
    .#{$prefix}-header__features--nav-modal,
    .#{$prefix}-header__features--search-modal {
        position: fixed;
        left: 0;
        top: 0;
        right: 0;
        bottom: 0;
        display: flex;
        justify-content: center;
        padding-left: 1rem;
        padding-right: 1rem;
        background-color: var(--cwf-header__features--modal--background-color);
        @include style.z-index("modal");
    
        --cwf-header__features--modal--background-color: #{$features--modal__background-color};
    
        @include media.reduced(transparency) {
            --cwf-header__features--modal--background-color: #{$features--modal__background-color--reduced-transparency};
        }
    
        @include media.reduced(transparency, no-preference) {
            --cwf-header__features--modal--background-color: #{$features--modal__background-color};
        }
    
        &[aria-hidden="false"] {
            @include animation.animation--fadeIn;
        }
    
        &[aria-hidden="true"] {
            @include animation.animation--fadeOut;
        }
    
        $exit__background-color: style.color("black") !default;
        $exit__background-color--active: style.color("black") !default;
        $exit__background-color--desktop: style.opacity("black", 50%) !default;
        $exit__color: style.color("white") !default;
    
        .#{$prefix}-header__exit {
            position: absolute;
            top: 0;
            right: 0;
            display: flex;
            align-items: center;
            justify-content: space-evenly;
            min-width: 128px;
            height: 64px;
            padding: 0;
            border: none;
            background-color: var(--cwf-header__exit--background-color);
            font-size: 1rem;
            font-weight: 700;
            color: var(--cwf-header__exit--color);
            @include animation.transition(background-color);
            --cwf-header__exit--background-color: #{$exit__background-color};
            --cwf-header__exit--color: #{$exit__color};
            --cwf-header__exit--active--background-color: #{$exit__background-color--active};
            --cwf-header__exit--desktop--background-color: #{$exit__background-color--desktop};
    
            &:hover,
            &:focus {
                background-color: var(--cwf-header__exit--active--background-color);
            }
            &:after {
                @include style.icon("\f00d"); // X-mark
                font-size: 1.5rem;
            }
    
            @include media.breakpoint {
                background-color: var(
                    --cwf-header__exit--desktop--background-color
                );
            }
        }
    }
    
    .#{$prefix}-header__features--search-modal {
        align-items: flex-start;
    
        @include media.breakpoint {
            align-items: center;
        }
    }
    
    .#{$prefix}-header__nav {
        display: flex;
        justify-content: flex-end;
        font-size: 0.889rem;
    }
    
    .#{$prefix}-header__features--search-modal .#{$prefix}-header__nav {
        display: none;
    }
    
    .#{$prefix}-header__features--nav-modal .#{$prefix}-header__nav {
        display: flex;
        flex-direction: column;
        width: 760px;
        max-width: 760px;
        margin-bottom: 1rem;
    
        @include media.breakpoint {
            margin-bottom: 0;
        }
    }
    
    .#{$prefix}-header__features--nav-modal[aria-hidden="false"]
        .#{$prefix}-header__nav {
        @include animation.animation--slideInUp;
    }
    
    .#{$prefix}-header__features--nav-modal[aria-hidden="true"]
        .#{$prefix}-header__nav {
        @include animation.animation--slideOutDown;
    }
    
    .#{$prefix}-header__nav .#{$prefix}-header__link {
        margin-right: 0.5rem;
        text-decoration: underline;
        &:hover,
        &:focus {
            text-decoration: none !important;
        }
        &:last-child {
            margin-right: 0;
        }
    }
    
    $features--nav-modal__link__border-color: style.color("white-darkest") !default;
    $features--nav-modal__link__background-color: style.color("white") !default;
    $features--nav-modal__link__color: style.color("gray-darkest") !default;
    $features--nav-modal__link__border-color--active: style.opacity(
        "black",
        12.5%
    ) !default;
    $features--nav-modal__link__background-color--active: theme.accent--background(
    
    ) !default;
    $features--nav-modal__link__color--active: theme.accent--foreground() !default;
    
    .#{$prefix}-header__features--nav-modal .#{$prefix}-header__link {
        display: block;
        width: 100%;
        max-width: 760px;
        margin-right: 0;
        padding: 1rem 1.5rem;
        border-bottom: 2px solid
            var(--cwf-header__features--nav-modal__link--border-color);
        background-color: var(
            --cwf-header__features--nav-modal__link--background-color
        );
        font-size: 1.5rem;
        text-decoration: none;
        color: var(--cwf-header__features--nav-modal__link--color);
        transition: none;
        --cwf-header__features--nav-modal__link--border-color: #{$features--nav-modal__link__border-color};
        --cwf-header__features--nav-modal__link--background-color: #{$features--nav-modal__link__background-color};
        --cwf-header__features--nav-modal__link--color: #{$features--nav-modal__link__color};
        --cwf-header__features--nav-modal__link--active--border-color: #{$features--nav-modal__link__border-color--active};
        --cwf-header__features--nav-modal__link--active--background-color: #{$features--nav-modal__link__background-color--active};
        --cwf-header__features--nav-modal__link--active--color: #{$features--nav-modal__link__color--active};
    
        &:hover,
        &:focus {
            border-color: var(
                --cwf-header__features--nav-modal__link--active--border-color
            );
            background-color: var(
                --cwf-header__features--nav-modal__link--active--background-color
            );
            color: var(--cwf-header__features--nav-modal__link--active--color);
        }
    
        &:focus {
            outline: none;
        }
    
        &:first-of-type {
            border-top-left-radius: 0.5rem;
            border-top-right-radius: 0.5rem;
        }
    
        &:last-of-type {
            border-bottom: none;
            border-bottom-right-radius: 0.5rem;
            border-bottom-left-radius: 0.5rem;
        }
    }
    
    .#{$prefix}-header__nav ~ .#{$prefix}-header__search {
        @include media.breakpoint {
            margin-top: 0.5rem;
        }
    }
    
    .#{$prefix}-header__search {
        position: relative;
        display: flex;
        flex: 1;
    }
    
    .#{$prefix}-header__features--nav-modal .#{$prefix}-header__search {
        display: none;
    }
    
    .#{$prefix}-header__features--search-modal .#{$prefix}-header__search {
        display: flex;
        flex-direction: column;
        width: 100%;
        min-width: 0;
        max-width: 760px;
        margin-top: calc(64px + 1rem);
    
        @include media.breakpoint {
            margin-top: 0;
        }
    }
    
    .#{$prefix}-header__features--search-modal[aria-hidden="false"]
        .#{$prefix}-header__search {
        @include animation.animation--slideInUp;
    }
    
    .#{$prefix}-header__features--search-modal[aria-hidden="true"]
        .#{$prefix}-header__search {
        @include animation.animation--slideOutDown;
    }
    
    .#{$prefix}-header__input {
        flex: 1;
        font-size: 0.889rem;
        padding: 0.5rem 2.25rem 0.5rem 0.75rem;
        border-width: 1px;
        border-style: solid;
        border-radius: 1.5rem;
        border-color: var(--cwf-header__input--border-color);
        background-color: var(--cwf-header__input--background-color);
        color: var(--cwf-header__input--color);
        @include animation.transition(border-color, background-color);
    
        &:hover {
            border-color: var(--cwf-header__input--hover--border-color);
        }
    
        &:focus {
            background-color: var(--cwf-header__input--focus--background-color);
        }
    }
    
    .#{$prefix}-header__input:focus,
    .#{$prefix}-header__input:focus:hover {
        outline: none;
        border-color: var(--cwf-header__input--focus--border-color);
    }
    
    $features--search-modal__input__border-color: style.color("white") !default;
    $features--search-modal__input__background-color: style.color("white") !default;
    $features--search-modal__input__color: style.color("gray-darkest") !default;
    $features--search-modal__input__border-color--active: style.color(
        "gray-darkest"
    ) !default;
    
    .#{$prefix}-header__features--search-modal .#{$prefix}-header__input {
        font-size: 1.5rem;
        padding: 1rem 4rem 1rem 1.5rem;
        border-width: 2px;
        border-color: var(
            --cwf-header__features--search-modal__input--border-color
        );
        border-radius: 3rem;
        background-color: var(
            --cwf-header__features--search-modal__input--background-color
        );
        color: var(--cwf-header__features--search-modal__input--color);
        transition: none;
        --cwf-header__features--search-modal__input--border-color: #{$features--search-modal__input__border-color};
        --cwf-header__features--search-modal__input--background-color: #{$features--search-modal__input__background-color};
        --cwf-header__features--search-modal__input--color: #{$features--search-modal__input__color};
        --cwf-header__features--search-modal__input--active--border-color: #{$features--search-modal__input__border-color--active};
    
        &:focus,
        &:focus:hover {
            border-color: var(
                --cwf-header__features--search-modal__input--active--border-color
            );
        }
    }
    
    .#{$prefix}-header__submit {
        position: absolute;
        right: 0.4rem;
        top: 50%;
        display: flex;
        justify-content: center;
        align-items: center;
        width: 1.5rem;
        height: 1.5rem;
        border: 1px solid var(--cwf-header__submit--border-color);
        border-radius: 100%;
        background-color: var(--cwf-header__input--background-color);
        font-size: 0.6rem;
        color: var(--cwf-header__label--color);
        transform: translateY(-50%);
        @include animation.transition(border-color, background-color, color);
    
        &:hover {
            border-color: var(--cwf-header__submit--hover--border-color);
        }
    
        &:focus {
            outline: none;
            border-color: var(--cwf-header__submit--focus--border-color);
            background-color: var(--cwf-header__input--focus--background-color);
            color: var(--cwf-header__input--color);
        }
    }
    
    $features--search-modal__submit__border-color: style.color(
        "white-darkest"
    ) !default;
    $features--search-modal__submit__background-color: style.color(
        "white"
    ) !default;
    $features--search-modal__submit__color: style.color("gray-lightest") !default;
    $features--search-modal__submit__border-color--hover: style.color(
        "gray-lightest"
    ) !default;
    $features--search-modal__submit__border-color--focus: style.color(
        "gray-darkest"
    ) !default;
    $features--search-modal__submit__color--focus: style.color(
        "gray-darkest"
    ) !default;
    
    .#{$prefix}-header__features--search-modal .#{$prefix}-header__submit {
        right: 0.5rem;
        width: 3rem;
        height: 3rem;
        border-width: 2px;
        border-color: var(
            --cwf-header__features--search-modal__submit--border-color
        );
        background-color: var(
            --cwf-header__features--search-modal__submit--background-color
        );
        font-size: 1rem;
        color: var(--cwf-header__features--search-modal__submit--color);
        transition: none;
        --cwf-header__features--search-modal__submit--border-color: #{$features--search-modal__submit__border-color};
        --cwf-header__features--search-modal__submit--background-color: #{$features--search-modal__submit__background-color};
        --cwf-header__features--search-modal__submit--color: #{$features--search-modal__submit__color};
        --cwf-header__features--search-modal__submit--hover--border-color: #{$features--search-modal__submit__border-color--hover};
        --cwf-header__features--search-modal__submit--focus--border-color: #{$features--search-modal__submit__border-color--focus};
        --cwf-header__features--search-modal__submit--focus--color: #{$features--search-modal__submit__color--focus};
    
        &:hover {
            border-color: var(
                --cwf-header__features--search-modal__submit--hover--border-color
            );
        }
    
        &:focus {
            border-color: var(
                --cwf-header__features--search-modal__submit--focus--border-color
            );
            color: var(--cwf-header__features--search-modal__submit--focus--color);
        }
    }
    
    .#{$prefix}-header__label {
        position: absolute;
        left: 0.75rem;
        font-size: 0.889rem;
        color: var(--cwf-header__label--color);
        transform: translateY(-50%);
        @include animation.animation--slideFadeIn(forwards);
    
        &:hover {
            cursor: text;
        }
    }
    
    .#{$prefix}-header__input:valid ~ .#{$prefix}-header__label {
        @include animation.animation--slideFadeOut(forwards);
    }
    
    $features--search-modal__label__color: style.color("gray-lightest") !default;
    
    .#{$prefix}-header__features--search-modal .#{$prefix}-header__label {
        left: 1.5rem;
        font-size: 1.5rem;
        color: var(--cwf-header__features--search-modal__label--color);
        --cwf-header__features--search-modal__label--color: #{$features--search-modal__label__color};
    }
    
  • URL: /components/raw/header/_index.scss
  • Filesystem Path: components/header/_index.scss
  • Size: 26.5 KB
  • Content:
    // The default component class
    import { Component } from '../../shared/component.js';
    
    // Lock/unlock document scrolling
    import { toggleScrollLock } from '../../shared/event.js';
    
    // Traps the focus to a specifc set of elements
    import { trap as trapFocus } from '../../shared/focus.js';
    
    // Provide functionality to the header
    export class Header extends Component {
        constructor({
            prefix = 'cwf',
            header = 'header',
            features = 'header__features',
            featuresNavModal = 'header__features--nav-modal',
            featuresSearchModal = 'header__features--search-modal',
            toggle = 'header__toggle',
            nav = 'header__nav',
            search = 'header__search',
            link = 'header__link',
            input = 'header__input',
            submit = 'header__submit',
            exit = 'header__exit'
        } = {}) {
            super({
                prefix,
                classes: {
                    header,
                    features,
                    featuresNavModal,
                    featuresSearchModal,
                    toggle,
                    nav,
                    search,
                    link,
                    input,
                    submit,
                    exit
                },
                references: {}
            });
    
            // Initialize the feature and modal state objects
            this.feature = {};
            this.modal = {};
    
            // Bind "this" to all methods that need it
            this.blurFeaturesModal = this.blurFeaturesModal.bind(this);
            this.modalOnClick = this.modalOnClick.bind(this);
            this.modalOnKeyDown = this.modalOnKeyDown.bind(this);
            this.closeFeaturesModal = this.closeFeaturesModal.bind(this);
            this.focusFeature = this.focusFeature.bind(this);
            this.navLinksOnKeyDown = this.navLinksOnKeyDown.bind(this);
            this.navLinksOnFocusBlur = this.navLinksOnFocusBlur.bind(this);
            this.onKeyDown = this.onKeyDown.bind(this);
    
            // Register the toggle scroll lock...
            this.toggleScrollLock = toggleScrollLock.bind(this);
            // ... and trap focus functions to this component
            this.trapFocus = trapFocus.bind(this);
        }
    
        get modalListener() {
            return super.stateListener(this.modal.open);
        }
    
        // Stop showing the features as a modal
        blurFeaturesModal() {
            // Remove the event listener that triggered this function,...
            this.references.features.wrapper[this.modalListener](
                'animationend',
                this.blurFeaturesModal,
                true
            );
            // ... remove the modal classes from the feature wrapper,...
            this.references.features.wrapper.className = this.classes.features;
            // ... remove the aria-hidden attribute from the feature wrapper,...
            this.references.features.wrapper.removeAttribute('aria-hidden');
            // ... toggle the scroll lock,...
            this.toggleScrollLock();
            // ... and reinitialize the global modal object
            this.modal = {};
        }
    
        // Close the features modal
        closeFeaturesModal() {
            // Set the modal to be closed...
            this.modal.open = false;
    
            // Set the features to be hidden...
            this.references.features.wrapper.setAttribute('aria-hidden', true);
            // ... and stop showing the features as a modal when its animation ends
            this.references.features.wrapper.addEventListener(
                'animationend',
                this.blurFeaturesModal,
                true
            );
    
            // Unfocus the search input
            document.activeElement.blur();
    
            // Remove the event listeners for the exit functionality
            this.references.features.wrapper[this.modalListener](
                'click',
                this.modalOnClick,
                true
            );
            this.references.features.exit[this.modalListener](
                'click',
                this.modalOnClick,
                true
            );
            document[this.modalListener]('keydown', this.modalOnKeyDown, true);
        }
    
        // Bind to the features' wrapper and exit button's click event when in modal view
        modalOnClick({ target, currentTarget }) {
            // Check to see if the target is the features wrapper...
            const targetIsFeatures = target === this.references.features.wrapper,
                // ... and the current target is the exit button
                currentTargetIsExit =
                    currentTarget === this.references.features.exit;
    
            // If the target is the features or the current target is the exit button, close the features modal
            if (targetIsFeatures || currentTargetIsExit)
                return this.closeFeaturesModal();
        }
    
        // Bind to the document's key-down event when in modal view
        modalOnKeyDown(event) {
            // Grab the key pressed from the event
            const { key } = event;
    
            // If "Escape" is pressed, close the features modal
            if (key === 'Escape') return this.closeFeaturesModal();
    
            // If "Tab" is pressed, trap the focus
            if (key === 'Tab') return this.trapFocus(this.feature.focusable, event);
        }
    
        // Open the features wrapper as a modal
        openFeaturesAsModal() {
            // Set the modal to be opened
            this.modal.open = true;
    
            // Prevent the body from scrolling
            this.toggleScrollLock();
    
            // Show the features wrapper as a modal...
            this.references.features.wrapper.classList.add(this.modal.class);
            // ... and set it to be visible in order to animate
            this.references.features.wrapper.setAttribute('aria-hidden', false);
    
            // Bind to the features' wrapper and exit button's click event...
            this.references.features.wrapper[this.modalListener](
                'click',
                this.modalOnClick,
                true
            );
            this.references.features.exit[this.modalListener](
                'click',
                this.modalOnClick,
                true
            );
            // ... and the document's key-down event
            document[this.modalListener]('keydown', this.modalOnKeyDown, true);
        }
    
        // Focus a feature's first element of a given selector
        focusFeatureElement(feature) {
            // Attempt to grab the feature element from the given selector...
            const element = feature.querySelector(this.feature.focus);
            // ... and if doesn't exist, do nothing else
            if (!element) return;
    
            // Otherwise, focus the element
            element.focus();
        }
    
        // Checks to see if the feature is within the viewport
        featureIsWithinViewport() {
            // If the feature element has not been set, do nothing,...
            if (!this.feature.element) return;
            // ... otherwise, check to see if the feature is within the viewport...
            const scroll = window.scrollY || window.pageYOffset,
                boundsTop =
                    this.feature.element.getBoundingClientRect().top + scroll,
                viewport = {
                    top: scroll,
                    bottom: scroll + window.innerHeight
                },
                bounds = {
                    top: boundsTop,
                    bottom: boundsTop + this.feature.element.clientHeight
                };
            // ... and return the findings
            return (
                (bounds.bottom >= viewport.top &&
                    bounds.bottom <= viewport.bottom) ||
                (bounds.top <= viewport.bottom && bounds.top >= viewport.top)
            );
        }
    
        // Checks to see if the feature is visible (e.g. not display: none;)
        featureIsVisible() {
            // If the feature element has not been set, do nothing,...
            if (!this.feature.element) return;
            // ... otherwise, return whether the feature is visible (e.g. not display: none;)
            return this.feature.element.offsetParent !== null;
        }
    
        // Select an argument based off the feature's type
        selectFromFeatureType(navOption, searchOption) {
            // If the feature element has not been set, do nothing
            if (!this.feature.element) return;
    
            // If the feature is a nav, return the nav argument
            if (
                this.references.features.nav &&
                this.feature.element === this.references.features.nav.element
            ) {
                return typeof navOption === 'function' ? navOption() : navOption;
            }
    
            // If the feature is a search, return the search argument
            if (
                this.references.features.search &&
                this.feature.element === this.references.features.search.element
            ) {
                return typeof searchOption === 'function'
                    ? searchOption()
                    : searchOption;
            }
        }
    
        // Return all focusable nav elements
        returnFocusableNavElements() {
            return [
                this.references.features.exit, // Exit button
                ...this.references.features.nav.links // Nav links
            ];
        }
    
        // Return all focusable search elements
        returnFocusableSearchElements() {
            return [
                this.references.features.exit, // Exit button
                this.references.features.search.input, // Search input
                this.references.features.search.submit // Search submit button
            ];
        }
    
        // Focus a header feature
        focusFeature({ currentTarget }) {
            // Attempt to grab the header feature...
            const id = currentTarget.getAttribute('aria-controls');
            this.feature.element = document.getElementById(id);
            // ... and if it doesn't exist, do nothing else
            if (!this.feature.element) return;
    
            // Figure out what element to focus and modal class to use based off its type
            this.feature.focus = this.selectFromFeatureType(
                this.selectors.link,
                this.selectors.input
            );
    
            // If the modal is already open, close it
            if (this.modal.open) return this.closeFeaturesModal();
    
            // If the feature is not within the viewport or is not visible, open the features as a modal
            if (!this.featureIsVisible() || !this.featureIsWithinViewport()) {
                // Find what elements are focusable within the feature...
                this.feature.focusable = this.selectFromFeatureType(
                    this.returnFocusableNavElements.bind(this),
                    this.returnFocusableSearchElements.bind(this)
                );
                // ... and modal class to be used,...
                this.modal.class = this.selectFromFeatureType(
                    this.classes.featuresNavModal,
                    this.classes.featuresSearchModal
                );
                // ... and open the features wrapper as a modal
                this.openFeaturesAsModal();
            }
    
            // Focus the feature's first element of the given selector
            this.focusFeatureElement(this.feature.element);
        }
    
        // Bind to every toggle's click event
        togglesOnClick() {
            // If no toggles exist, do nothing else
            if (!this.references.toggles.length) return;
    
            // For each toggle,...
            this.references.toggles.forEach((toggle) => {
                // ... open the features as a modal when clicked
                toggle[this.listener]('click', this.focusFeature);
            });
        }
    
        // Bind to every nav link's key-down event if focused
        navLinksOnKeyDown(event) {
            // Grab the type, current target, and key from the event
            const { type, currentTarget, key } = event;
    
            // If the type is focus/blur...
            if (['focus', 'blur'].includes(type)) {
                // ... add/remove key-down functionality respectively
                const listener =
                    type === 'focus' ? 'addEventListener' : 'removeEventListener';
                return currentTarget[listener]('keydown', this.navLinksOnKeyDown);
            }
    
            // Always assign Home/End keys for navigation,...
            const navigation = ['Home', 'End'];
            // ... figure out what previous/next keys make sense based on if a modal is open,...
            const previous = this.modal.open ? 'ArrowUp' : 'ArrowLeft',
                next = this.modal.open ? 'ArrowDown' : 'ArrowRight';
            // ... and push them to the navigation array
            navigation.push(previous, next);
    
            // If the current key pressed is not within the navigation, do nothing else
            if (!navigation.includes(key)) return;
    
            // Otherwise, prevent the event's default behavior...
            event.preventDefault();
            // ... and handle the navigation
            switch (key) {
                case 'Home':
                    // Home = Focus the first nav link
                    return this.references.features.nav.links[0].focus();
                case 'End':
                    // End = Focus the last nav link
                    return this.references.features.nav.links[
                        this.references.features.nav.links.length - 1
                    ].focus();
                default:
                    // Arrow keys = trap focus within the nav links
                    return this.trapFocus(
                        this.references.features.nav.links,
                        key === previous
                    );
            }
        }
    
        // Bind to every nav link's focus/blur event
        navLinksOnFocusBlur() {
            // If a nav...
            if (!this.references.features.nav) return;
            // ... or nav links exist, do nothing else
            if (!this.references.features.nav.links) return;
    
            // For each nav link...
            this.references.features.nav.links.forEach((link) => {
                // ... bind to its key-down even when focused
                link[this.listener]('focus', this.navLinksOnKeyDown);
                link[this.listener]('blur', this.navLinksOnKeyDown);
            });
        }
    
        // Bind to the document's key presses
        onKeyDown(event) {
            // Check to see if the user is typing within a text area or input...
            const focused = document.activeElement,
                inputting = ['TEXTAREA', 'INPUT'].includes(focused.tagName);
            // ... and if so, do nothing else
            if (inputting) return;
    
            // Grab the key pressed from the event
            const { key } = event;
    
            // Attempt to find a toggle with a key shorcut that matches the one pressed...
            const toggle = this.references.toggles.find((toggle) => {
                return toggle.getAttribute('aria-keyshortcuts') === key;
            });
            // ... and if doesn't exist, do nothing else
            if (!toggle) return;
    
            // Prevent the default event behavior...
            event.preventDefault();
            // ... and virtually click the toggle
            toggle.click();
        }
    
        // Bind/unbind to the document's key-down event
        documentOnKeyDown() {
            document[this.listener]('keydown', this.onKeyDown);
        }
    
        // Get the nav
        getNav(features) {
            // Attempt to grab the nav...
            const nav = features.querySelector(this.selectors.nav);
            // ... and if none exists, do nothing else
            if (!nav) return;
    
            // Otherwise, store it and its relevant children
            this.references.features.nav = {};
            this.references.features.nav.element = nav;
            this.references.features.nav.links = Array.from(
                nav.querySelectorAll(this.selectors.link)
            );
        }
    
        // Get the search
        getSearch(features) {
            // Attempt to grab the search...
            const search = features.querySelector(this.selectors.search);
            // ... and if none exists, do nothing else
            if (!search) return;
    
            // Otherwise, store it and its relevant children
            this.references.features.search = {};
            this.references.features.search.element = search;
            this.references.features.search.input = search.querySelector(
                this.selectors.input
            );
            this.references.features.search.submit = search.querySelector(
                this.selectors.submit
            );
        }
    
        // Mount/unmount the header functionality
        mount(run) {
            super.mount(run);
    
            // Attempt to find the header...
            const header =
                this.references.header ||
                document.querySelector(this.selectors.header);
            // ... and if it doesn't exist, do nothing,...
            if (!header) return;
            // ... otherwise, save it as a reference
            this.references.header = header;
    
            // Attempt to find the header features...
            this.references.features = this.references.features || {};
            const features =
                this.references.features.wrapper ||
                header.querySelector(this.selectors.features);
            // ... and if doesn't exist, do nothing,...
            if (!features) return;
            // ... otherwise, save it as a reference
            this.references.features.wrapper = features;
    
            // Grab all toggles...
            this.references.toggles =
                this.references.toggles ||
                Array.from(header.querySelectorAll(this.selectors.toggle));
            // ... and the feature's exit button
            this.references.features.exit =
                this.references.features.exit ||
                features.querySelector(this.selectors.exit);
    
            // If mounting,...
            if (run) {
                // ... attempt to grab the nav...
                this.getNav(features);
                // ... and search features
                this.getSearch(features);
            }
    
            // Bind to every toggle's click event,...
            this.togglesOnClick();
            // ... every nav link's focus/blur events (if mounting),...
            if (run) this.navLinksOnFocusBlur();
            // ... and the document's key-down event
            this.documentOnKeyDown();
    
            if (!run) this.references = {};
        }
    }
    
  • URL: /components/raw/header/index.js
  • Filesystem Path: components/header/index.js
  • Size: 17.5 KB

Header

The header component should be used at the top of every page to show the title of the website and provide a link to the homepage. The header also includes a few optional elements such a link to a unit’s parent unit, a search form and place for quick links.

Variations

The header component has the ability to have a white, gray, gold or dark background. By default the background will be white.

The header component also has the option to display in compact mode, which will reduce the font size of the unit name and the padding size of the wrapping block (while at a md breakpoint or larger). When using the header in compact mode it is suggested to not include the optional parent unit information. Consider using compact mode for sites or applications with internal audiences.

T4 implementation

The header is implemented in T4 as the “Header” Compass content type, meaning its classes retain the .cwf- prefix.

The number of quick links allowed in the content type is limited to 5; Any links beyond the limit will be ignored.

Areas

This content type should only be used within the global “Site-Header” section to have it displayed globally within the header area.

Injectors

In the “Injectors” field of the header content type, the following injectors can be used:

  • id:{custom_id} - Overrides the default, T4 ID of the header with a custom ID.
  • class:{custom_classes} - Adds custom classes to the header.
  • style:{custom_styles} - Adds custom styles to a style attribute of the header.
  • before:{custom_html} - Adds custom HTML before the header.
  • after:{custom_html} - Adds custom HTML after the header.