My pattern library

10.1 #Navigation.swipeable-container Swipeable container

Toggle full screen Open in new window Toggle example guides Toggle HTML markup

A container with a flexbox in it such that

  • one item at a time is shown,
  • swiping left/right moves to the previous/next element.

I adapted it from a very good tutorial on CSS Tricks. One difference is that, in that tutorial, overflow-x: hidden; is applied to to the whole <body> element, because each of the children of the swipeable container is meant to fit the whole body. In my solution, I want to be able to make any HTML element swipeable, so I wrap the solution from CSS Tricks in another element (which can be <body> but needn't be), and that is the one targeted by the module.

Note: The plain <div>, which is inside the .swipeable-container and contains the children to be swiped through, is necessary, because it is the flexbox, not the outer .swipeable-container.

Note: This module is accompained by some JavaScript.

Example
Markup
<div class="swipeable-container" style="margin: auto; width: 80%; border: 25px solid black;">
  <div>
    <div style="border-width: 25px; border-style: solid; border-color: red;">
      <div style="margin: auto; width: 8em; height: 5em; background-color: red;"></div>
    </div>
    <div style="border-width: 25px; border-style: solid; border-color: green;">
      <div style="margin: auto; width: 12em; height: 10em; background-color: green;"></div>
    </div>
    <div style="border-width: 25px; border-style: solid; border-color: blue;">
      <div style="margin: auto; width: 16em; height: 15em; background-color: blue;"></div>
    </div>
  </div>
</div>
Source: css/modules/swipeable-container.css, line 1

10.2 #Navigation.tabs Tab-based nav

Toggle full screen Open in new window Toggle example guides Toggle HTML markup

A list of radio buttons is used to select and show one of several tabs.

Manually entering the hash pointing to one of the elements selects and shows the tab where that element belongs and performs an action on that element.

This is fundamentally a JavaScript module. The only CSS directive is to make all tabs initially not visible.

To set up the tab based navigation, structure the markup as per the example below, and then make the following call:

  setupTabs({
    // Actions (impure functions, as they read from/write to the global state represented by the HTML):
    //  - read
    getDefaultHash: () => /* string */,
    getElemById: id => /* obj */,
    getHash: () => /* string */,
    getId: e => /* string */,
    getParentTab: elem => /* object */,
    getTabPage: name => /* object */,
    getTabInput: name => /* object */,
    getVisibleTabs: () => /* objects */,
    isTab: str => /* bool */,
    //  - write
    actionOnElem: e => { /* action */ },
    check: cb => { /* action */ },
    hide: e => { /* action */ },
    show: e => { /* action */ },
    setClickCallback: f => { /* action */ },
    setHash: h => { /* action */ },
    setHashChangeCallback: f => { /* action */ },
    tabInputCallback: eventObj => { /* action */ }
  });

Note: The example below exercises the logic of the module, but shows it differently:

  • tabs are "shown" by boxing them and coloring their contents green
  • tabs are "hidden" by removing the box and coloring their contents red
  • if an element is selected, it's background is lightgreen
Example
tab 1
  • elem 1.1
  • elem 1.2
  • elem 1.3
tab 2
  • elem 2.1
  • elem 2.2
  • elem 2.3
tab 3
  • elem 3.1
  • elem 3.2
  • elem 3.3
Markup
<script>
(function() {
  // Load the script
  const script = document.createElement("script");
  script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js';
  script.type = 'text/javascript';
  script.addEventListener('load', () => {
    console.log(`jQuery ${$.fn.jquery} has been loaded successfully!`);
    // use jQuery below
    // the stuff above is taken from [1]
    $(document).ready(() => {
      // some helper function
      const prependHash = str => str == '' ? str : '#' + str;
      // in this demo shown/hidden are actually mapped to green-and-boxed/red-not-boxed
      $('.tab-page').css('display', 'initial');
      $('.tab-page').css('color', 'red');
      $('.tab-page li').css('display', 'table')
      setupTabs({
        //  - read
        getDefaultHash: () => $('nav input[checked]').val().toString(),
        getElemById: id => $('#' + id),
        getHash: () => prependHash($('#hash').val().toString()),
        getId: e => e.attr('id'),
        getParentTab: elem => dropDashTab($('#' + elem).parents('.tab-page').attr('id')),
        getTabPage: name => $('.tab-page' + '#' + name + '-tab'),
        getTabInput: name => $('nav input[value=' + name + ']'),
        getVisibleTabs: () => $('.tab-page').filter(
          function( index ) {
            return this.style.color === 'green';
          }
        ),
        isTab: str => $('#' + str).filter('.tab-page').length != 0,
        //  - write
        actionOnElem: e => {
          $('.tab-page li').css('font-weight', 'normal');
          $('.tab-page li').css('background-color', 'transparent');
          e.css('font-weight', 'bold');
          e.css('background-color', 'lightgreen');
        },
        check: cb => { cb.prop('checked', true); },
        hide: e => {
          $('.tab-page li').css('font-weight', 'normal');
          $('.tab-page li').css('background-color', 'transparent');
          e.css('color', 'red');
          e.children().first().css('border-style', 'none');
        },
        show: e => {
          $('.tab-page li').css('font-weight', 'normal');
          $('.tab-page li').css('background-color', 'transparent');
          e.css('color', 'green');
          e.children().first().css('border-style', 'solid');
        },
        setClickCallback: f => { $('nav input').click(f); },
        setHash: h => { $('#hash').val(h); },
        setHashChangeCallback: f => { $('#hash').change(f); },
        tabInputCallback: eventObj => {
          $('#hash').val($(eventObj.currentTarget).val().toString()).change();
        }
      });
      $('#hash').change();
    });
    // the stuff below is taken from [1]
  });
  document.head.appendChild(script);
})();
// [1]: https://stackoverflow.com/a/10113434/5825294
</script>
<label for="name">URL:</label>
<input type="text"
       value="sch://some/url/#"
       minlength="0" maxlength="8" size="10"
       readonly>
<input type="text"
       id="hash"
       name="name"
       placeholder="hash (without the leading #)">
<nav>
  <ul>
    <li>
      <input type="radio" name="nav-tabs" id="tab1-radio" value="tab1">
      <label for="tab1-radio">show only tab 1</label>
    </li>
    <li>
      <input type="radio" name="nav-tabs" id="tab2-radio" value="tab2" checked>
      <label for="tab2-radio">show only tab 2 (this is the default)</label>
    </li>
    <li>
      <input type="radio" name="nav-tabs" id="tab3-radio" value="tab3">
      <label for="tab3-radio">show only tab 3</label>
    </li>
  </ul>
</nav>
<div>
  <div class="tab-page" id="tab1-tab">
    tab 1
    <ul>
      <li id="elem11">elem 1.1</li>
      <li id="elem12">elem 1.2</li>
      <li id="elem13">elem 1.3</li>
    </ul>
  </div>
  <div class="tab-page" id="tab2-tab">
    tab 2
    <ul>
      <li id="elem21">elem 2.1</li>
      <li id="elem22">elem 2.2</li>
      <li id="elem23">elem 2.3</li>
    </ul>
  </div>
  <div class="tab-page" id="tab3-tab">
    tab 3
    <ul>
      <li id="elem31">elem 3.1</li>
      <li id="elem32">elem 3.2</li>
      <li id="elem33">elem 3.3</li>
    </ul>
  </div>
</div>
Source: css/modules/tabs.css, line 1