Archive

Archive for May, 2020

The Anatomy of a Tablist Component in Vanilla JavaScript Versus React

May 5th, 2020 No comments
Console warnings from eslint-jsx-a11y-plugin

If you follow the undercurrent of the JavaScript community, there seems to be a divide as of late. It goes back over a decade. Really, this sort of strife has always been. Perhaps it is human nature.

Whenever a popular framework gains traction, you inevitably see people comparing it to rivals. I suppose that is to be expected. Everyone has a particular favorite.

Lately, the framework everyone loves (to hate?) is React. You often see it pitted against others in head-to-head blog posts and feature comparison matrices of enterprise whitepapers. Yet a few years ago, it seemed like jQuery would forever be king of the hill.

Frameworks come and go. To me, what is more interesting is when React — or any JS framework for that matter — gets pitted against the programming language itself. Because of course, under the hood, it is all built atop JS.

The two are not inherently at odds. I would even go so far as to say that if you do not have a good handle on JS fundamentals, you probably are not going to reap the full benefits of using React. It can still be helpful, similar to using a jQuery plugin without understanding its internals. But I feel like React presupposes more JS familiarity.

HTML is equally important. There exists a fair bit of FUD around how React affects accessibility. I think this narrative is inaccurate. In fact, the ESLint JSX a11y plugin will warn of possible accessibility violations in the console.

ESLint warnings about empty tags

Recently, an annual study of the top 1 million sites was released. It shows that for sites using JS frameworks, there is an increased likelihood of accessibility problems. This is correlation, not causation.

This does not necessarily mean that the frameworks caused these errors, but it does indicate that home pages with these frameworks had more errors than on average.

In a manner of speaking, React’s magic incantations work regardless of whether you recognize the words. Ultimately, you are still responsible for the outcome.

Philosophical musings aside, I am a firm believer in choosing the best tool for the job. Sometimes, that means building a single page app with a Jamstack approach. Or maybe a particular project is better suited to offloading HTML rendering to the server, where it has historically been handled.

Either way, there inevitably comes the need for JS to augment the user experience. At Reaktiv Studios, to that end I have been attempting to keep most of our React components in sync with our “flat HTML” approach. I have been writing commonly used functionality in vanilla JS as well. This keeps our options open, so that our clients are free to choose. It also allows us to reuse the same CSS.

If I may, I would like to share how I built our and React components. I will also demonstrate how I wrote the same functionality without using a framework.

Hopefully, this lesson will feel like we are making a layered cake. Let us first start with the base markup, then cover the vanilla JS, and finish with how it works in React.

For reference, you can tinker with our live examples:

Reaktiv Studios UI components

Flat HTML examples

Since we need JavaScript to make interactive widgets either way, I figured the easiest approach — from a server side implementation standpoint — would be to require only the bare minimum HTML. The rest can be augmented with JS.

The following are examples of markup for tabs and accordion components, showing a before/after comparison of how JS affects the DOM.

I have added id="TABS_ID" and id="ACCORDION_ID" for demonstrative purposes. This is to make it more obvious what is happening. But the JS that I will be explaining automatically generates unique IDs if nothing is supplied in the HTML. It would work fine either way, with or without an id specified.

Tabs (without ARIA)

<div class="tabs" id="TABS_ID">
  <ul class="tabs__list">
    <li class="tabs__item">
      Tab 1
    </li>
    <!-- .tabs__item -->

    <li class="tabs__item">
      Tab 2
    </li>
    <!-- .tabs__item -->

    <li class="tabs__item" disabled>
      Tab 3 (disabled)
    </li>
    <!-- .tabs__item -->
  </ul>
  <!-- .tabs__list -->

  <div class="tabs__panel">
    <p>
      Tab 1 content
    </p>
  </div>
  <!-- .tabs__panel -->

  <div class="tabs__panel">
    <p>
      Tab 2 content
    </p>
  </div>
  <!-- .tabs__panel -->

  <div class="tabs__panel">
    <p>
      NOTE: This tab is disabled.
    </p>
  </div>
  <!-- .tabs__panel -->
</div>
<!-- .tabs -->

Tabs (with ARIA)

<div class="tabs" id="TABS_ID">
  <ul class="tabs__list" role="tablist">
    <li
      aria-controls="tabpanel_TABS_ID_0"
      aria-selected="false"
      class="tabs__item"
      id="tab_TABS_ID_0"
      role="tab"
      tabindex="0"
    >
      Tab 1
    </li>
    <!-- .tabs__item -->

    <li
      aria-controls="tabpanel_TABS_ID_1"
      aria-selected="true"
      class="tabs__item"
      id="tab_TABS_ID_1"
      role="tab"
      tabindex="0"
    >
      Tab 2
    </li>
    <!-- .tabs__item -->

    <li
      aria-controls="tabpanel_TABS_ID_2"
      aria-disabled="true"
      aria-selected="false"
      class="tabs__item"
      disabled
      id="tab_TABS_ID_2"
      role="tab"
    >
      Tab 3 (disabled)
    </li>
    <!-- .tabs__item -->
  </ul>
  <!-- .tabs__list -->

  <div
    aria-hidden="true"
    aria-labelledby="tab_TABS_ID_0"
    class="tabs__panel"
    id="tabpanel_TABS_ID_0"
    role="tabpanel"
  >
    <p>
      Tab 1 content
    </p>
  </div>
  <!-- .tabs__panel -->

  <div
    aria-hidden="false"
    aria-labelledby="tab_TABS_ID_1"
    class="tabs__panel"
    id="tabpanel_TABS_ID_1"
    role="tabpanel"
  >
    <p>
      Tab 2 content
    </p>
  </div>
  <!-- .tabs__panel -->

  <div
    aria-hidden="true"
    aria-labelledby="tab_TABS_ID_2"
    class="tabs__panel"
    id="tabpanel_TABS_ID_2"
    role="tabpanel"
  >
    <p>
      NOTE: This tab is disabled.
    </p>
  </div>
  <!-- .tabs__panel -->
</div>
<!-- .tabs -->

Accordion (without ARIA)

<div class="accordion" id="ACCORDION_ID">
  <div class="accordion__item">
    Tab 1
  </div>
  <!-- .accordion__item -->

  <div class="accordion__panel">
    <p>
      Tab 1 content
    </p>
  </div>
  <!-- .accordion__panel -->

  <div class="accordion__item">
    Tab 2
  </div>
  <!-- .accordion__item -->

  <div class="accordion__panel">
    <p>
      Tab 2 content
    </p>
  </div>
  <!-- .accordion__panel -->

  <div class="accordion__item" disabled>
    Tab 3 (disabled)
  </div>
  <!-- .accordion__item -->

  <div class="accordion__panel">
    <p>
      NOTE: This tab is disabled.
    </p>
  </div>
  <!-- .accordion__panel -->
</div>
<!-- .accordion -->

Accordion (with ARIA)

<div
  aria-multiselectable="true"
  class="accordion"
  id="ACCORDION_ID"
  role="tablist"
>
  <div
    aria-controls="tabpanel_ACCORDION_ID_0"
    aria-selected="true"
    class="accordion__item"
    id="tab_ACCORDION_ID_0"
    role="tab"
    tabindex="0"
  >
    <i aria-hidden="true" class="accordion__item__icon"></i>
    Tab 1
  </div>
  <!-- .accordion__item -->

  <div
    aria-hidden="false"
    aria-labelledby="tab_ACCORDION_ID_0"
    class="accordion__panel"
    id="tabpanel_ACCORDION_ID_0"
    role="tabpanel"
  >
    <p>
      Tab 1 content
    </p>
  </div>
  <!-- .accordion__panel -->

  <div
    aria-controls="tabpanel_ACCORDION_ID_1"
    aria-selected="false"
    class="accordion__item"
    id="tab_ACCORDION_ID_1"
    role="tab"
    tabindex="0"
  >
    <i aria-hidden="true" class="accordion__item__icon"></i>
    Tab 2
  </div>
  <!-- .accordion__item -->

  <div
    aria-hidden="true"
    aria-labelledby="tab_ACCORDION_ID_1"
    class="accordion__panel"
    id="tabpanel_ACCORDION_ID_1"
    role="tabpanel"
  >
    <p>
      Tab 2 content
    </p>
  </div>
  <!-- .accordion__panel -->

  <div
    aria-controls="tabpanel_ACCORDION_ID_2"
    aria-disabled="true"
    aria-selected="false"
    class="accordion__item"
    disabled
    id="tab_ACCORDION_ID_2"
    role="tab"
  >
    <i aria-hidden="true" class="accordion__item__icon"></i>
    Tab 3 (disabled)
  </div>
  <!-- .accordion__item -->

  <div
    aria-hidden="true"
    aria-labelledby="tab_ACCORDION_ID_2"
    class="accordion__panel"
    id="tabpanel_ACCORDION_ID_2"
    role="tabpanel"
  >
    <p>
      NOTE: This tab is disabled.
    </p>
  </div>
  <!-- .accordion__panel -->
</div>
<!-- .accordion -->

Vanilla JavaScript examples

Okay. Now that we have seen the aforementioned HTML examples, let us walk through how we get from before to after.

First, I want to cover a few helper functions. These will make more sense in a bit. I figure it is best to get them documented first, so we can stay focused on the rest of the code once we dive in further.

File: getDomFallback.js

This function provides common DOM properties and methods as no-op, rather than having to make lots of typeof foo.getAttribute checks and whatnot. We could forego those types of confirmations altogether.

Since live HTML changes can be a potentially volatile environment, I always feel a bit safer making sure my JS is not bombing out and taking the rest of the page with it. Here is what that function looks like. It simply returns an object with the DOM equivalents of falsy results.

/*
  Helper to mock DOM methods, for
  when an element might not exist.
*/
const getDomFallback = () => {
  return {
    // Props.
    children: [],
    className: '',
    classList: {
      contains: () => false,
    },
    id: '',
    innerHTML: '',
    name: '',
    nextSibling: null,
    previousSibling: null,
    outerHTML: '',
    tagName: '',
    textContent: '',

    // Methods.
    appendChild: () => Object.create(null),
    cloneNode: () => Object.create(null),
    closest: () => null,
    createElement: () => Object.create(null),
    getAttribute: () => null,
    hasAttribute: () => false,
    insertAdjacentElement: () => Object.create(null),
    insertBefore: () => Object.create(null),
    querySelector: () => null,
    querySelectorAll: () => [],
    removeAttribute: () => undefined,
    removeChild: () => Object.create(null),
    replaceChild: () => Object.create(null),
    setAttribute: () => undefined,
  };
};

// Export.
export { getDomFallback };

File: unique.js

This function is a poor man’s UUID equivalent.

It generates a unique string that can be used to associate DOM elements with one another. It is handy, because then the author of an HTML page does not have to ensure that every tabs and accordion component have unique IDs. In the previous HTML examples, this is where TABS_ID and ACCORDION_ID would typically contain the randomly generated numeric strings instead.

// ==========
// Constants.
// ==========

const BEFORE = '0.';
const AFTER = '';

// ==================
// Get unique string.
// ==================

const unique = () => {
  // Get prefix.
  let prefix = Math.random();
  prefix = String(prefix);
  prefix = prefix.replace(BEFORE, AFTER);

  // Get suffix.
  let suffix = Math.random();
  suffix = String(suffix);
  suffix = suffix.replace(BEFORE, AFTER);

  // Expose string.
  return `${prefix}_${suffix}`;
};

// Export.
export { unique };

On larger JavaScript projects, I would typically use npm install uuid. But since we are keeping this simple and do not require cryptographic parity, concatenating two lightly edited Math.random() numbers will suffice for our string uniqueness needs.

File: tablist.js

This file does the bulk of the work. What is cool about it, if I do say so myself, is that there are enough similarities between a tabs component and an accordion that we can handle both with the same *.js file. Go ahead and scroll through the entirety, and then we will break down what each function does individually.

// Helpers.
import { getDomFallback } from './getDomFallback';
import { unique } from './unique';

// ==========
// Constants.
// ==========

// Boolean strings.
const TRUE = 'true';
const FALSE = 'false';

// ARIA strings.
const ARIA_CONTROLS = 'aria-controls';
const ARIA_DISABLED = 'aria-disabled';
const ARIA_LABELLEDBY = 'aria-labelledby';
const ARIA_HIDDEN = 'aria-hidden';
const ARIA_MULTISELECTABLE = 'aria-multiselectable';
const ARIA_SELECTED = 'aria-selected';

// Attribute strings.
const DISABLED = 'disabled';
const ID = 'id';
const ROLE = 'role';
const TABLIST = 'tablist';
const TABINDEX = 'tabindex';

// Event strings.
const CLICK = 'click';
const KEYDOWN = 'keydown';

// Key strings.
const ENTER = 'enter';
const FUNCTION = 'function';

// Tag strings.
const LI = 'li';

// Selector strings.
const ACCORDION_ITEM_ICON = 'accordion__item__icon';
const ACCORDION_ITEM_ICON_SELECTOR = `.${ACCORDION_ITEM_ICON}`;

const TAB = 'tab';
const TAB_SELECTOR = `[${ROLE}=${TAB}]`;

const TABPANEL = 'tabpanel';
const TABPANEL_SELECTOR = `[${ROLE}=${TABPANEL}]`;

const ACCORDION = 'accordion';
const TABLIST_CLASS_SELECTOR = '.accordion, .tabs';
const TAB_CLASS_SELECTOR = '.accordion__item, .tabs__item';
const TABPANEL_CLASS_SELECTOR = '.accordion__panel, .tabs__panel';

// ===========
// Get tab ID.
// ===========

const getTabId = (id = '', index = 0) => {
  return `tab_${id}_${index}`;
};

// =============
// Get panel ID.
// =============

const getPanelId = (id = '', index = 0) => {
  return `tabpanel_${id}_${index}`;
};

// ==============
// Click handler.
// ==============

const globalClick = (event = {}) => {
  // Get target.
  const { key = '', target = getDomFallback() } = event;

  // Get parent.
  const { parentNode = getDomFallback(), tagName = '' } = target;

  // Set later.
  let wrapper = getDomFallback();

  /*
    =====
    NOTE:
    =====

    We test for this, because the method does
    not exist on `document.documentElement`.
  */
  if (typeof target.closest === FUNCTION) {
    // Get wrapper.
    wrapper = target.closest(TABLIST_CLASS_SELECTOR) || getDomFallback();
  }

  // Is `<li>`?
  const isListItem = tagName.toLowerCase() === LI;

  // Is multi?
  const isMulti = wrapper.getAttribute(ARIA_MULTISELECTABLE) === TRUE;

  // Valid key?
  const isValidKey = !key || key.toLowerCase() === ENTER;

  // Valid target?
  const isValidTarget =
    !target.hasAttribute(DISABLED) &&
    target.getAttribute(ROLE) === TAB &&
    parentNode.getAttribute(ROLE) === TABLIST;

  // Valid event?
  const isValidEvent = isValidKey && isValidTarget;

  // Continue?
  if (isValidEvent) {
    // Get panel.
    const panelId = target.getAttribute(ARIA_CONTROLS);
    const panel = wrapper.querySelector(`#${panelId}`) || getDomFallback();

    // Get booleans.
    let boolPanel = panel.getAttribute(ARIA_HIDDEN) !== TRUE;
    let boolTab = target.getAttribute(ARIA_SELECTED) !== TRUE;

    // List item?
    if (isListItem) {
      boolPanel = FALSE;
      boolTab = TRUE;
    }

    // [aria-multiselectable="false"]
    if (!isMulti) {
      // Get tabs & panels.
      const childTabs = wrapper.querySelectorAll(TAB_SELECTOR);
      const childPanels = wrapper.querySelectorAll(TABPANEL_SELECTOR);

      // Loop through tabs.
      childTabs.forEach((tab = getDomFallback()) => {
        tab.setAttribute(ARIA_SELECTED, FALSE);
      });

      // Loop through panels.
      childPanels.forEach((panel = getDomFallback()) => {
        panel.setAttribute(ARIA_HIDDEN, TRUE);
      });
    }

    // Set individual tab.
    target.setAttribute(ARIA_SELECTED, boolTab);

    // Set individual panel.
    panel.setAttribute(ARIA_HIDDEN, boolPanel);
  }
};

// ====================
// Add ARIA attributes.
// ====================

const addAriaAttributes = () => {
  // Get elements.
  const allWrappers = document.querySelectorAll(TABLIST_CLASS_SELECTOR);

  // Loop through.
  allWrappers.forEach((wrapper = getDomFallback()) => {
    // Get attributes.
    const { id = '', classList } = wrapper;
    const parentId = id || unique();

    // Is accordion?
    const isAccordion = classList.contains(ACCORDION);

    // Get tabs & panels.
    const childTabs = wrapper.querySelectorAll(TAB_CLASS_SELECTOR);
    const childPanels = wrapper.querySelectorAll(TABPANEL_CLASS_SELECTOR);

    // Add ID?
    if (!wrapper.getAttribute(ID)) {
      wrapper.setAttribute(ID, parentId);
    }

    // Add multi?
    if (isAccordion && wrapper.getAttribute(ARIA_MULTISELECTABLE) !== FALSE) {
      wrapper.setAttribute(ARIA_MULTISELECTABLE, TRUE);
    }

    // ===========================
    // Loop through tabs & panels.
    // ===========================

    for (let index = 0; index < childTabs.length; index++) {
      // Get elements.
      const tab = childTabs[index] || getDomFallback();
      const panel = childPanels[index] || getDomFallback();

      // Get IDs.
      const tabId = getTabId(parentId, index);
      const panelId = getPanelId(parentId, index);

      // ===================
      // Add tab attributes.
      // ===================

      // Tab: add icon?
      if (isAccordion) {
        // Get icon.
        let icon = tab.querySelector(ACCORDION_ITEM_ICON_SELECTOR);

        // Create icon?
        if (!icon) {
          icon = document.createElement(I);
          icon.className = ACCORDION_ITEM_ICON;
          tab.insertAdjacentElement(AFTER_BEGIN, icon);
        }

        // [aria-hidden="true"]
        icon.setAttribute(ARIA_HIDDEN, TRUE);
      }

      // Tab: add id?
      if (!tab.getAttribute(ID)) {
        tab.setAttribute(ID, tabId);
      }

      // Tab: add controls?
      if (!tab.getAttribute(ARIA_CONTROLS)) {
        tab.setAttribute(ARIA_CONTROLS, panelId);
      }

      // Tab: add selected?
      if (!tab.getAttribute(ARIA_SELECTED)) {
        const bool = !isAccordion && index === 0;

        tab.setAttribute(ARIA_SELECTED, bool);
      }

      // Tab: add role?
      if (tab.getAttribute(ROLE) !== TAB) {
        tab.setAttribute(ROLE, TAB);
      }

      // Tab: add tabindex?
      if (tab.hasAttribute(DISABLED)) {
        tab.removeAttribute(TABINDEX);
        tab.setAttribute(ARIA_DISABLED, TRUE);
      } else {
        tab.setAttribute(TABINDEX, 0);
      }

      // Tab: first item?
      if (index === 0) {
        // Get parent.
        const { parentNode = getDomFallback() } = tab;

        /*
          We do this here, instead of outside the loop.

          The top level item isn't always the `tablist`.

          The accordion UI only has `<dl>`, whereas
          the tabs UI has both `<div>` and `<ul>`.
        */
        if (parentNode.getAttribute(ROLE) !== TABLIST) {
          parentNode.setAttribute(ROLE, TABLIST);
        }
      }

      // =====================
      // Add panel attributes.
      // =====================

      // Panel: add ID?
      if (!panel.getAttribute(ID)) {
        panel.setAttribute(ID, panelId);
      }

      // Panel: add hidden?
      if (!panel.getAttribute(ARIA_HIDDEN)) {
        const bool = isAccordion || index !== 0;

        panel.setAttribute(ARIA_HIDDEN, bool);
      }

      // Panel: add labelled?
      if (!panel.getAttribute(ARIA_LABELLEDBY)) {
        panel.setAttribute(ARIA_LABELLEDBY, tabId);
      }

      // Panel: add role?
      if (panel.getAttribute(ROLE) !== TABPANEL) {
        panel.setAttribute(ROLE, TABPANEL);
      }
    }
  });
};

// =====================
// Remove global events.
// =====================

const unbind = () => {
  document.removeEventListener(CLICK, globalClick);
  document.removeEventListener(KEYDOWN, globalClick);
};

// ==================
// Add global events.
// ==================

const init = () => {
  // Add attributes.
  addAriaAttributes();

  // Prevent doubles.
  unbind();

  document.addEventListener(CLICK, globalClick);
  document.addEventListener(KEYDOWN, globalClick);
};

// ==============
// Bundle object.
// ==============

const tablist = {
  init,
  unbind,
};

// =======
// Export.
// =======

export { tablist };

Function: getTabId and getPanelId

These two functions are used to create individually unique IDs for elements in a loop, based on an existing (or generated) parent ID. This is helpful to ensure matching values for attributes like aria-controls="…" and aria-labelledby="…". Think of those as the accessibility equivalents of , telling the browser which elements are related to one another.

const getTabId = (id = '', index = 0) => {
  return `tab_${id}_${index}`;
};
const getPanelId = (id = '', index = 0) => {
  return `tabpanel_${id}_${index}`;
};

Function: globalClick

This is a click handler that is applied at the document level. That means we are not having to manually add click handlers to a number of elements. Instead, we use event bubbling to listen for clicks further down in the document, and allow them to propagate up to the top. Conveniently, this is also how we can handle keyboard events such as the Enter key being pressed. Both are necessary to have an accessible UI.

In the first part of the function, we destructure key and target from the incoming event. Next, we destructure the parentNode and tagName from the target.

Then, we attempt to get the wrapper element. This would be the one with either class="tabs" or class="accordion". Because we might actually be clicking on the ancestor element highest in the DOM tree — which exists but possibly does not have the *.closest(…) method — we do a typeof check. If that function exists, we attempt to get the element. Even still, we might come up without a match. So we have one more getDomFallback to be safe.

// Get target.
const { key = '', target = getDomFallback() } = event;

// Get parent.
const { parentNode = getDomFallback(), tagName = '' } = target;

// Set later.
let wrapper = getDomFallback();

/*
  =====
  NOTE:
  =====

  We test for this, because the method does
  not exist on `document.documentElement`.
*/
if (typeof target.closest === FUNCTION) {
  // Get wrapper.
  wrapper = target.closest(TABLIST_CLASS_SELECTOR) || getDomFallback();
}

Then, we store whether or not the tag that was clicked is a

  • . Likewise, we store a boolean about whether the wrapper element has aria-multiselectable="true". I will get back to that. We need this info later on.

    We also interrogate the event a bit, to determine if it was triggered by the user pressing a key. If so, then we are only interested if that key was Enter. We also determine if the click happened on a relevant target. Remember, we are using event bubbling so really the user could have clicked anything.

    We want to make sure it:

    • Is not disabled
    • Has role="tab"
    • Has a parent element with role="tablist"

    Then we bundle up our event and target booleans into one, as isValidEvent.

    // Is `<li>`?
    const isListItem = tagName.toLowerCase() === LI;
    
    // Is multi?
    const isMulti = wrapper.getAttribute(ARIA_MULTISELECTABLE) === TRUE;
    
    // Valid key?
    const isValidKey = !key || key.toLowerCase() === ENTER;
    
    // Valid target?
    const isValidTarget =
      !target.hasAttribute(DISABLED) &&
      target.getAttribute(ROLE) === TAB &&
      parentNode.getAttribute(ROLE) === TABLIST;
    
    // Valid event?
    const isValidEvent = isValidKey && isValidTarget;
    

    Assuming the event is indeed valid, we make it past our next if check. Now, we are concerned with getting the role="tabpanel" element with an id that matches our tab’s aria-controls="…".

    Once we have got it, we check whether the panel is hidden, and if the tab is selected. Basically, we first presuppose that we are dealing with an accordion and flip the booleans to their opposites.

    This is also where our earlier isListItem boolean comes into play. If the user is clicking an

  • then we know we are dealing with tabs, not an accordion. In which case, we want to flag our panel as being visible (via aria-hiddden="false") and our tab as being selected (via aria-selected="true").

    Also, we want to ensure that either the wrapper has aria-multiselectable="false" or is completely missing aria-multiselectable. If that is the case, then we loop through all neighboring role="tab" and all role="tabpanel" elements and set them to their inactive states. Finally, we arrive at setting the previously determined booleans for the individual tab and panel pairing.

    // Continue?
    if (isValidEvent) {
      // Get panel.
      const panelId = target.getAttribute(ARIA_CONTROLS);
      const panel = wrapper.querySelector(`#${panelId}`) || getDomFallback();
    
      // Get booleans.
      let boolPanel = panel.getAttribute(ARIA_HIDDEN) !== TRUE;
      let boolTab = target.getAttribute(ARIA_SELECTED) !== TRUE;
    
      // List item?
      if (isListItem) {
        boolPanel = FALSE;
        boolTab = TRUE;
      }
    
      // [aria-multiselectable="false"]
      if (!isMulti) {
        // Get tabs & panels.
        const childTabs = wrapper.querySelectorAll(TAB_SELECTOR);
        const childPanels = wrapper.querySelectorAll(TABPANEL_SELECTOR);
    
        // Loop through tabs.
        childTabs.forEach((tab = getDomFallback()) => {
          tab.setAttribute(ARIA_SELECTED, FALSE);
        });
    
        // Loop through panels.
        childPanels.forEach((panel = getDomFallback()) => {
          panel.setAttribute(ARIA_HIDDEN, TRUE);
        });
      }
    
      // Set individual tab.
      target.setAttribute(ARIA_SELECTED, boolTab);
    
      // Set individual panel.
      panel.setAttribute(ARIA_HIDDEN, boolPanel);
    }

    Function: addAriaAttributes

    The astute reader might be thinking:

    You said earlier that we start with the most bare possible markup, yet the globalClick function was looking for attributes that would not be there. Why would you lie!?

    Or perhaps not, for the astute reader would have also noticed the function named addAriaAttributes. Indeed, this function does exactly what it says on the tin. It breathes life into the base DOM structure, by adding all the requisite aria-* and role attributes.

    This not only makes the UI inherently more accessible to assistive technologies, but it also ensures the functionality actually works. I prefer to build vanilla JS things this way, rather than pivoting on class="…" for interactivity, because it forces me to think about the entirety of the user experience, beyond what I can see visually.

    First off, we get all elements on the page that have class="tabs" and/or class="accordion". Then we check if we have something to work with. If not, then we would exit our function here. Assuming we do have a list, we loop through each of the wrapping elements and pass them into the scope of our function as wrapper.

    // Get elements.
    const allWrappers = document.querySelectorAll(TABLIST_CLASS_SELECTOR);
    
    // Loop through.
    allWrappers.forEach((wrapper = getDomFallback()) => {
      /*
        NOTE: Cut, for brevity.
      */
    });
    

    Inside the scope of our looping function, we destructure id and classList from wrapper. If there is no ID, then we generate one via unique(). We set a boolean flag, to identify if we are working with an accordion. This is used later.

    We also get decendants of wrapper that are tabs and panels, via their class name selectors.

    Tabs:

    • class="tabs__item" or
    • class="accordion__item"

    Panels:

    • class="tabs__panel" or
    • class="accordion__panel"

    We then set the wrapper’s id if it does not already have one.

    If we are dealing with an accordion that lacks aria-multiselectable="false", we set its flag to true. Reason being, if developers are reaching for an accordion UI paradigm — and also have tabs available to them, which are inherently mutually exclusive — then the safer assumption is that the accordion should support expanding and collapsing of several panels.

    // Get attributes.
    const { id = '', classList } = wrapper;
    const parentId = id || unique();
    
    // Is accordion?
    const isAccordion = classList.contains(ACCORDION);
    
    // Get tabs & panels.
    const childTabs = wrapper.querySelectorAll(TAB_CLASS_SELECTOR);
    const childPanels = wrapper.querySelectorAll(TABPANEL_CLASS_SELECTOR);
    
    // Add ID?
    if (!wrapper.getAttribute(ID)) {
      wrapper.setAttribute(ID, parentId);
    }
    
    // Add multi?
    if (isAccordion && wrapper.getAttribute(ARIA_MULTISELECTABLE) !== FALSE) {
      wrapper.setAttribute(ARIA_MULTISELECTABLE, TRUE);
    }
    

    Next, we loop through tabs. Wherein, we also handle our panels.

    You may be wondering why this is an old school for loop, instead of a more modern *.forEach. The reason is that we want to loop through two NodeList instances: tabs and panels. Assuming they each map 1-to-1 we know they both have the same *.length. This allows us to have one loop instead of two.

    Let us peer inside of the loop. First, we get unique IDs for each tab and panel. These would look like one of the two following scenarios. These are used later on, to associate tabs with panels and vice versa.

    • tab_WRAPPER_ID_0 or
      tab_GENERATED_STRING_0
    • tabpanel_WRAPPER_ID_0 or
      tabpanel_GENERATED_STRING_0
    for (let index = 0; index < childTabs.length; index++) {
      // Get elements.
      const tab = childTabs[index] || getDomFallback();
      const panel = childPanels[index] || getDomFallback();
    
      // Get IDs.
      const tabId = getTabId(parentId, index);
      const panelId = getPanelId(parentId, index);
    
      /*
        NOTE: Cut, for brevity.
      */
    }
    

    As we loop through, we first ensure that an expand/collapse icon exists. We create it if necessary, and set it to aria-hidden="true" since it is purely decorative.

    Next, we check on attributes for the current tab. If an id="…" does not exist on the tab, we add it. Likewise, if aria-controls="…" does not exist we add that as well, pointing to our newly created panelId.

    You will notice there is a little pivot here, checking if we do not have aria-selected and then further determining if we are not in the context of an accordion and if the index is 0. In that case, we want to make our first tab look selected. The reason is that though an accordion can be fully collapsed, tabbed content cannot. There is always at least one panel visible.

    Then we ensure that role="tab" exists.

    It is worth noting we do some extra work, based on whether the tab is disabled. If so, we remove tabindex so that the tab cannot receive :focus. If the tab is not disabled, we add tabindex="0" so that it can receive :focus.

    We also set aria-disabled="true", if need be. You might be wondering if that is redundant. But it is necessary to inform assistive technologies that the tab is not interactive. Since our tab is either a

    or

  • , it technically cannot be disabled like an . Our styles pivot on [disabled], so we get that for free. Plus, it is less cognitive overhead (as a developer creating HTML) to only worry about one attribute.

    ?? Fun Fact: It is also worth noting the use of hasAttribute(…) to detect disabled, instead of getAttribute(…). This is because the mere presence of disabled will cause form elements to be disabled.

    If the HTML is compiled, via tools such as Parcel

    • Markup like this:
    • Is changed to this:

    In which case, getting the attribute is still a falsy string.

    In the days of XHTML, that would have been disabled="disabled". But really, it was only ever the existence of the attribute that mattered. Not its value. That is why we simply test if the element has the disabled attribute.

    Lastly, we check if we are on the first iteration of our loop where index is 0. If so, we go up one level to the parentNode. If that element does not have role="tablist", then we add it.

    We do this via parentNode instead of wrapper because in the context of tabs (not accordion) there is a

      element around the tab

    • that needs role="tablist". In the case of an accordion, it would be the outermost
      ancestor. This code accounts for both.

      // Tab: add icon?
      if (isAccordion) {
        // Get icon.
        let icon = tab.querySelector(ACCORDION_ITEM_ICON_SELECTOR);
      
        // Create icon?
        if (!icon) {
          icon = document.createElement(I);
          icon.className = ACCORDION_ITEM_ICON;
          tab.insertAdjacentElement(AFTER_BEGIN, icon);
        }
      
        // [aria-hidden="true"]
        icon.setAttribute(ARIA_HIDDEN, TRUE);
      }
      
      // Tab: add id?
      if (!tab.getAttribute(ID)) {
        tab.setAttribute(ID, tabId);
      }
      
      // Tab: add controls?
      if (!tab.getAttribute(ARIA_CONTROLS)) {
        tab.setAttribute(ARIA_CONTROLS, panelId);
      }
      
      // Tab: add selected?
      if (!tab.getAttribute(ARIA_SELECTED)) {
        const bool = !isAccordion && index === 0;
      
        tab.setAttribute(ARIA_SELECTED, bool);
      }
      
      // Tab: add role?
      if (tab.getAttribute(ROLE) !== TAB) {
        tab.setAttribute(ROLE, TAB);
      }
      
      // Tab: add tabindex?
      if (tab.hasAttribute(DISABLED)) {
        tab.removeAttribute(TABINDEX);
        tab.setAttribute(ARIA_DISABLED, TRUE);
      } else {
        tab.setAttribute(TABINDEX, 0);
      }
      
      // Tab: first item?
      if (index === 0) {
        // Get parent.
        const { parentNode = getDomFallback() } = tab;
      
        /*
          We do this here, instead of outside the loop.
      
          The top level item isn't always the `tablist`.
      
          The accordion UI only has `<dl>`, whereas
          the tabs UI has both `<div>` and `<ul>`.
        */
        if (parentNode.getAttribute(ROLE) !== TABLIST) {
          parentNode.setAttribute(ROLE, TABLIST);
        }
      }

      Continuing within the earlier for loop, we add attributes for each panel. We add an id if needed. We also set aria-hidden to either true or false depending on the context of being an accordion (or not).

      Likewise, we ensure that our panel points back to its tab trigger via aria-labelledby="…", and that role="tabpanel" has been set.

      // Panel: add ID?
      if (!panel.getAttribute(ID)) {
        panel.setAttribute(ID, panelId);
      }
      
      // Panel: add hidden?
      if (!panel.getAttribute(ARIA_HIDDEN)) {
        const bool = isAccordion || index !== 0;
      
        panel.setAttribute(ARIA_HIDDEN, bool);
      }
      
      // Panel: add labelled?
      if (!panel.getAttribute(ARIA_LABELLEDBY)) {
        panel.setAttribute(ARIA_LABELLEDBY, tabId);
      }
      
      // Panel: add role?
      if (panel.getAttribute(ROLE) !== TABPANEL) {
        panel.setAttribute(ROLE, TABPANEL);
      }
      

      At the very end of the file, we have a few setup and teardown functions. As a way to play nicely with other JS that might be in the page, we provide an unbind function that removes our global event listeners. It can be called by itself, via tablist.unbind() but is mostly there so that we can unbind() before (re-)binding. That way we prevent doubling up.

      Inside our init function, we call addAriaAttributes() which modifies the DOM to be accessible. We then call unbind() and then add our event listeners to the document.

      Finally, we bundle both methods into a parent object and export it under the name tablist. That way, when dropping it into a flat HTML page, we can call tablist.init() when we are ready to apply our functionality.

      // =====================
      // Remove global events.
      // =====================
      
      const unbind = () => {
        document.removeEventListener(CLICK, globalClick);
        document.removeEventListener(KEYDOWN, globalClick);
      };
      
      // ==================
      // Add global events.
      // ==================
      
      const init = () => {
        // Add attributes.
        addAriaAttributes();
      
        // Prevent doubles.
        unbind();
      
        document.addEventListener(CLICK, globalClick);
        document.addEventListener(KEYDOWN, globalClick);
      };
      
      // ==============
      // Bundle object.
      // ==============
      
      const tablist = {
        init,
        unbind,
      };
      
      // =======
      // Export.
      // =======
      
      export { tablist };

      React examples

      There is a scene in Batman Begins where Lucius Fox (played by Morgan Freeman) explains to a recovering Bruce Wayne (Christian Bale) the scientific steps he took to save his life after being poisoned.

      Lucius Fox: “I analyzed your blood, isolating the receptor compounds and the protein-based catalyst.”

      Bruce Wayne: “Am I meant to understand any of that?”

      Lucius Fox: “Not at all, I just wanted you to know how hard it was. Bottom line, I synthesized an antidote.”

      Morgan Freeman and Christian Bale, sitting inside the Batmobile
      “How do I configure Webpack?”

      ? When working with a framework, I think in those terms.

      Now that we know “hard” it is — not really, but humor me — to do raw DOM manipulation and event binding, we can better appreciate the existence of an antidote. React abstracts a lot of that complexity away, and handles it for us automatically.

      File: Tabs.js

      Now that we are diving into React examples, we will start with the component.

      // =============
      // Used like so…
      // =============
      
      <Tabs>
        <div label="Tab 1">
          <p>
            Tab 1 content
          </p>
        </div>
        <div label="Tab 2">
          <p>
            Tab 2 content
          </p>
        </div>
      </Tabs>

      Here is the content from our Tabs.js file. Note that in React parlance, it is standard practice to name the file with the same capitalization as its export default component.

      We start out with the same getTabId and getPanelId functions as in our vanilla JS approach, because we still need to make sure to accessibly map tabs to components. Take a look at the entirey of the code, and then we will continue to break it down.

      import React, { useState } from 'react';
      import PropTypes from 'prop-types';
      import { v4 as uuid } from 'uuid';
      import cx from 'classnames';
      
      // UI.
      import Render from './Render';
      
      // ===========
      // Get tab ID.
      // ===========
      
      const getTabId = (id = '', index = 0) => {
        return `tab_${id}_${index}`;
      };
      
      // =============
      // Get panel ID.
      // =============
      
      const getPanelId = (id = '', index = 0) => {
        return `tabpanel_${id}_${index}`;
      };
      
      // ==========
      // Is active?
      // ==========
      
      const getIsActive = ({ activeIndex = null, index = null, list = [] }) => {
        // Index matches?
        const isMatch = index === parseFloat(activeIndex);
      
        // Is first item?
        const isFirst = index === 0;
      
        // Only first item exists?
        const onlyFirstItem = list.length === 1;
      
        // Item doesn't exist?
        const badActiveItem = !list[activeIndex];
      
        // Flag as active?
        const isActive = isMatch || onlyFirstItem || (isFirst && badActiveItem);
      
        // Expose boolean.
        return !!isActive;
      };
      
      getIsActive.propTypes = {
        activeIndex: PropTypes.number,
        index: PropTypes.number,
        list: PropTypes.array,
      };
      
      // ================
      // Get `<ul>` list.
      // ================
      
      const getTabsList = ({ activeIndex = null, id = '', list = [], setActiveIndex = () => {} }) => {
        // Build new list.
        const newList = list.map((item = {}, index) => {
          // =========
          // Get data.
          // =========
      
          const { props: itemProps = {} } = item;
          const { disabled = null, label = '' } = itemProps;
          const idPanel = getPanelId(id, index);
          const idTab = getTabId(id, index);
          const isActive = getIsActive({ activeIndex, index, list });
      
          // =======
          // Events.
          // =======
      
          const handleClick = (event = {}) => {
            const { key = '' } = event;
      
            if (!disabled) {
              // Early exit.
              if (key && key.toLowerCase() !== 'enter') {
                return;
              }
      
              setActiveIndex(index);
            }
          };
      
          // ============
          // Add to list.
          // ============
      
          return (
            <li
              aria-controls={idPanel}
              aria-disabled={disabled}
              aria-selected={isActive}
              className="tabs__item"
              disabled={disabled}
              id={idTab}
              key={idTab}
              role="tab"
              tabIndex={disabled ? null : 0}
              // Events.
              onClick={handleClick}
              onKeyDown={handleClick}
            >
              {label || `${index + 1}`}
            </li>
          );
        });
      
        // ==========
        // Expose UI.
        // ==========
      
        return (
          <Render if={newList.length}>
            <ul className="tabs__list" role="tablist">
              {newList}
            </ul>
          </Render>
        );
      };
      
      getTabsList.propTypes = {
        activeIndex: PropTypes.number,
        id: PropTypes.string,
        list: PropTypes.array,
        setActiveIndex: PropTypes.func,
      };
      
      // =================
      // Get `<div>` list.
      // =================
      
      const getPanelsList = ({ activeIndex = null, id = '', list = [] }) => {
        // Build new list.
        const newList = list.map((item = {}, index) => {
          // =========
          // Get data.
          // =========
      
          const { props: itemProps = {} } = item;
          const { children = '', className = null, style = null } = itemProps;
          const idPanel = getPanelId(id, index);
          const idTab = getTabId(id, index);
          const isActive = getIsActive({ activeIndex, index, list });
      
          // =============
          // Get children.
          // =============
      
          let content = children || item;
      
          if (typeof content === 'string') {
            content = <p>{content}</p>;
          }
      
          // =================
          // Build class list.
          // =================
      
          const classList = cx({
            tabs__panel: true,
            [String(className)]: className,
          });
      
          // ==========
          // Expose UI.
          // ==========
      
          return (
            <div
              aria-hidden={!isActive}
              aria-labelledby={idTab}
              className={classList}
              id={idPanel}
              key={idPanel}
              role="tabpanel"
              style={style}
            >
              {content}
            </div>
          );
        });
      
        // ==========
        // Expose UI.
        // ==========
      
        return newList;
      };
      
      getPanelsList.propTypes = {
        activeIndex: PropTypes.number,
        id: PropTypes.string,
        list: PropTypes.array,
      };
      
      // ==========
      // Component.
      // ==========
      
      const Tabs = ({
        children = '',
        className = null,
        selected = 0,
        style = null,
        id: propsId = uuid(),
      }) => {
        // ===============
        // Internal state.
        // ===============
      
        const [id] = useState(propsId);
        const [activeIndex, setActiveIndex] = useState(selected);
      
        // =================
        // Build class list.
        // =================
      
        const classList = cx({
          tabs: true,
          [String(className)]: className,
        });
      
        // ===============
        // Build UI lists.
        // ===============
      
        const list = Array.isArray(children) ? children : [children];
      
        const tabsList = getTabsList({
          activeIndex,
          id,
          list,
          setActiveIndex,
        });
      
        const panelsList = getPanelsList({
          activeIndex,
          id,
          list,
        });
      
        // ==========
        // Expose UI.
        // ==========
      
        return (
          <Render if={list[0]}>
            <div className={classList} id={id} style={style}>
              {tabsList}
              {panelsList}
            </div>
          </Render>
        );
      };
      
      Tabs.propTypes = {
        children: PropTypes.node,
        className: PropTypes.string,
        id: PropTypes.string,
        selected: PropTypes.number,
        style: PropTypes.object,
      };
      
      export default Tabs;

      Function: getIsActive

      Due to a component always having something active and visible, this function contains some logic to determine whether an index of a given tab should be the lucky winner. Essentially, in sentence form the logic goes like this.

      This current tab is active if:

      • Its index matches the activeIndex, or
      • The tabs UI has only one tab, or
      • It is the first tab, and the activeIndex tab does not exist.
      const getIsActive = ({ activeIndex = null, index = null, list = [] }) => {
        // Index matches?
        const isMatch = index === parseFloat(activeIndex);
      
        // Is first item?
        const isFirst = index === 0;
      
        // Only first item exists?
        const onlyFirstItem = list.length === 1;
      
        // Item doesn't exist?
        const badActiveItem = !list[activeIndex];
      
        // Flag as active?
        const isActive = isMatch || onlyFirstItem || (isFirst && badActiveItem);
      
        // Expose boolean.
        return !!isActive;
      };

      Function: getTabsList

      This function generates the clickable

    • UI, and returns it wrapped in a parent
        . It assigns all the relevant aria-* and role attributes, and handles binding the onClickand onKeyDown events. When an event is triggered, setActiveIndex is called. This updates the component’s internal state.

        It is noteworthy how the content of the

      • is derived. That is passed in as
        children of the parent component. Though this is not a real concept in flat HTML, it is a handy way to think about the relationship of the content. The children of that

        become the the innards of our role="tabpanel" later.

        const getTabsList = ({ activeIndex = null, id = '', list = [], setActiveIndex = () => {} }) => {
          // Build new list.
          const newList = list.map((item = {}, index) => {
            // =========
            // Get data.
            // =========
        
            const { props: itemProps = {} } = item;
            const { disabled = null, label = '' } = itemProps;
            const idPanel = getPanelId(id, index);
            const idTab = getTabId(id, index);
            const isActive = getIsActive({ activeIndex, index, list });
        
            // =======
            // Events.
            // =======
        
            const handleClick = (event = {}) => {
              const { key = '' } = event;
        
              if (!disabled) {
                // Early exit.
                if (key && key.toLowerCase() !== 'enter') {
                  return;
                }
        
                setActiveIndex(index);
              }
            };
        
            // ============
            // Add to list.
            // ============
        
            return (
              <li
                aria-controls={idPanel}
                aria-disabled={disabled}
                aria-selected={isActive}
                className="tabs__item"
                disabled={disabled}
                id={idTab}
                key={idTab}
                role="tab"
                tabIndex={disabled ? null : 0}
                // Events.
                onClick={handleClick}
                onKeyDown={handleClick}
              >
                {label || `${index + 1}`}
              </li>
            );
          });
        
          // ==========
          // Expose UI.
          // ==========
        
          return (
            <Render if={newList.length}>
              <ul className="tabs__list" role="tablist">
                {newList}
              </ul>
            </Render>
          );
        };

        Function: getPanelsList

        This function parses the incoming children of the top level component and extracts the content. It also makes use of getIsActive to determine whether (or not) to apply aria-hidden="true". As one might expect by now, it adds all the other relevant aria-* and role attributes too. It also applies any extra className or style that was passed in.

        It also is “smart” enough to wrap any string content — anything lacking a wrapping tag already — in

        tags for consistency.

        const getPanelsList = ({ activeIndex = null, id = '', list = [] }) => {
          // Build new list.
          const newList = list.map((item = {}, index) => {
            // =========
            // Get data.
            // =========
        
            const { props: itemProps = {} } = item;
            const { children = '', className = null, style = null } = itemProps;
            const idPanel = getPanelId(id, index);
            const idTab = getTabId(id, index);
            const isActive = getIsActive({ activeIndex, index, list });
        
            // =============
            // Get children.
            // =============
        
            let content = children || item;
        
            if (typeof content === 'string') {
              content = <p>{content}</p>;
            }
        
            // =================
            // Build class list.
            // =================
        
            const classList = cx({
              tabs__panel: true,
              [String(className)]: className,
            });
        
            // ==========
            // Expose UI.
            // ==========
        
            return (
              <div
                aria-hidden={!isActive}
                aria-labelledby={idTab}
                className={classList}
                id={idPanel}
                key={idPanel}
                role="tabpanel"
                style={style}
              >
                {content}
              </div>
            );
          });
        
          // ==========
          // Expose UI.
          // ==========
        
          return newList;
        };

        Function: Tabs

        This is the main component. It sets an internal state for an id, to essentially cache any generated uuid() so that it does not change during the lifecycle of the component. React is finicky about its key attributes (in the previous loops) changing dynamically, so this ensures they remain static once set.

        We also employ useState to track the currently selected tab, and pass down a setActiveIndex function to each

      • to monitor when they are clicked. After that, it is pretty straightfowrard. We call getTabsList and getPanelsList to build our UI, and then wrap it all up in
        .

        It accepts any wrapper level className or style, in case anyone wants further tweaks during implementation. Providing other developers (as consumers) this flexibility means that the likelihood of needing to make further edits to the core component is lower. Lately, I have been doing this as a “best practice” for all components I create.

        const Tabs = ({
          children = '',
          className = null,
          selected = 0,
          style = null,
          id: propsId = uuid(),
        }) => {
          // ===============
          // Internal state.
          // ===============
        
          const [id] = useState(propsId);
          const [activeIndex, setActiveIndex] = useState(selected);
        
          // =================
          // Build class list.
          // =================
        
          const classList = cx({
            tabs: true,
            [String(className)]: className,
          });
        
          // ===============
          // Build UI lists.
          // ===============
        
          const list = Array.isArray(children) ? children : [children];
        
          const tabsList = getTabsList({
            activeIndex,
            id,
            list,
            setActiveIndex,
          });
        
          const panelsList = getPanelsList({
            activeIndex,
            id,
            list,
          });
        
          // ==========
          // Expose UI.
          // ==========
        
          return (
            <Render if={list[0]}>
              <div className={classList} id={id} style={style}>
                {tabsList}
                {panelsList}
              </div>
            </Render>
          );
        };

        If you are curious about the function, you can read more about that in this example.

        File: Accordion.js

        // =============
        // Used like so…
        // =============
        
        <Accordion>
          <div label="Tab 1">
            <p>
              Tab 1 content
            </p>
          </div>
          <div label="Tab 2">
            <p>
              Tab 2 content
            </p>
          </div>
        </Accordion>

        As you may have deduced — due to the vanilla JS example handling both tabs and accordion — this file has quite a few similarities to how Tabs.js works.

        Rather than belabor the point, I will simply provide the file’s contents for completeness and then speak about the specific areas in which the logic differs. So, take a gander at the contents and I will explain what makes quirky.

        import React, { useState } from 'react';
        import PropTypes from 'prop-types';
        import { v4 as uuid } from 'uuid';
        import cx from 'classnames';
        
        // UI.
        import Render from './Render';
        
        // ===========
        // Get tab ID.
        // ===========
        
        const getTabId = (id = '', index = 0) => {
          return `tab_${id}_${index}`;
        };
        
        // =============
        // Get panel ID.
        // =============
        
        const getPanelId = (id = '', index = 0) => {
          return `tabpanel_${id}_${index}`;
        };
        
        // ==============================
        // Get `tab` and `tabpanel` list.
        // ==============================
        
        const getTabsAndPanelsList = ({
          activeItems = {},
          id = '',
          isMulti = true,
          list = [],
          setActiveItems = () => {},
        }) => {
          // Build new list.
          const newList = [];
        
          // Loop through.
          list.forEach((item = {}, index) => {
            // =========
            // Get data.
            // =========
        
            const { props: itemProps = {} } = item;
        
            const {
              children = '',
              className = null,
              disabled = null,
              label = '',
              style = null,
            } = itemProps;
        
            const idPanel = getPanelId(id, index);
            const idTab = getTabId(id, index);
            const isActive = !!activeItems[index];
        
            // =======
            // Events.
            // =======
        
            const handleClick = (event = {}) => {
              const { key = '' } = event;
        
              if (!disabled) {
                // Early exit.
                if (key && key.toLowerCase() !== 'enter') {
                  return;
                }
        
                // Keep active items?
                const state = isMulti ? activeItems : null;
        
                // Update active item.
                const newState = {
                  ...state,
                  [index]: !activeItems[index],
                };
        
                // Set active item.
                setActiveItems(newState);
              }
            };
        
            // =============
            // Get children.
            // =============
        
            let content = children || item;
        
            if (typeof content === 'string') {
              content = <p>{content}</p>;
            }
        
            // =================
            // Build class list.
            // =================
        
            const classList = cx({
              accordion__panel: true,
              [String(className)]: className,
            });
        
            // ========
            // Add tab.
            // ========
        
            newList.push(
              <div
                aria-controls={idPanel}
                aria-disabled={disabled}
                aria-selected={isActive}
                className="accordion__item"
                disabled={disabled}
                id={idTab}
                key={idTab}
                role="tab"
                tabIndex={disabled ? null : 0}
                // Events.
                onClick={handleClick}
                onKeyDown={handleClick}
              >
                <i aria-hidden="true" className="accordion__item__icon" />
                {label || `${index + 1}`}
              </div>
            );
        
            // ==========
            // Add panel.
            // ==========
        
            newList.push(
              <div
                aria-hidden={!isActive}
                aria-labelledby={idTab}
                className={classList}
                id={idPanel}
                key={idPanel}
                role="tabpanel"
                style={style}
              >
                {content}
              </div>
            );
          });
        
          // ==========
          // Expose UI.
          // ==========
        
          return newList;
        };
        
        getTabsAndPanelsList.propTypes = {
          activeItems: PropTypes.object,
          id: PropTypes.string,
          isMulti: PropTypes.bool,
          list: PropTypes.array,
          setActiveItems: PropTypes.func,
        };
        
        // ==========
        // Component.
        // ==========
        
        const Accordion = ({
          children = '',
          className = null,
          isMulti = true,
          selected = {},
          style = null,
          id: propsId = uuid(),
        }) => {
          // ===============
          // Internal state.
          // ===============
        
          const [id] = useState(propsId);
          const [activeItems, setActiveItems] = useState(selected);
        
          // =================
          // Build class list.
          // =================
        
          const classList = cx({
            accordion: true,
            [String(className)]: className,
          });
        
          // ===============
          // Build UI lists.
          // ===============
        
          const list = Array.isArray(children) ? children : [children];
        
          const tabsAndPanelsList = getTabsAndPanelsList({
            activeItems,
            id,
            isMulti,
            list,
            setActiveItems,
          });
        
          // ==========
          // Expose UI.
          // ==========
        
          return (
            <Render if={list[0]}>
              <div
                aria-multiselectable={isMulti}
                className={classList}
                id={id}
                role="tablist"
                style={style}
              >
                {tabsAndPanelsList}
              </div>
            </Render>
          );
        };
        
        Accordion.propTypes = {
          children: PropTypes.node,
          className: PropTypes.string,
          id: PropTypes.string,
          isMulti: PropTypes.bool,
          selected: PropTypes.object,
          style: PropTypes.object,
        };
        
        export default Accordion;

        Function: handleClick

        While most of our logic is similar to , it differs in how it stores the currently active tab.

        Since are always mutually exclusive, we only really need a single numeric index. Easy peasy.

        However, because an can have concurrently visible panels — or be used in a mutually exclusive manner — we need to represent that to useState in a way that could handle both.

        If you were beginning to think…

        “I would store that in an object.”

        …then congrats. You are right!

        This function does a quick check to see if isMulti has been set to true. If so, we use the spread syntax to apply the existing activeItems to our newState object. We then set the current index to its boolean opposite.

        const handleClick = (event = {}) => {
          const { key = '' } = event;
        
          if (!disabled) {
            // Early exit.
            if (key && key.toLowerCase() !== 'enter') {
              return;
            }
        
            // Keep active items?
            const state = isMulti ? activeItems : null;
        
            // Update active item.
            const newState = {
              ...state,
              [index]: !activeItems[index],
            };
        
            // Set active item.
            setActiveItems(newState);
          }
        };

        For reference, here is how our activeItems object looks if only the first accordion panel is active and a user clicks the second. Both indexes would be set to true. This allows for viewing two expanded role="tabpanel" simultaneously.

        /*
          Internal representation
          of `activeItems` state.
        */
        
        {
          0: true,
          1: true,
        }
        

        Whereas if we were not operating in isMulti mode — when the wrapper has aria-multiselectable="false" — then activeItems would only ever contain one key/value pair.

        Because rather than spreading the current activeItems, we would be spreading null. That effectively wipes the slate clean, before recording the currently active tab.

        /*
          Internal representation
          of `activeItems` state.
        */
        
        {
          1: true,
        }
        

        Conclusion

        Still here? Awesome.

        Hopefully you found this article informative, and maybe even learned a bit more about accessibility and JS(X) along the way. For review, let us look one more time at our flat HTML example and and the React usage of our component. Here is a comparison of the markup we would write in a vanilla JS approach, versus the JSX it takes to generate the same thing.

        I am not saying that one is better than the other, but you can see how React makes it possible to distill things down into a mental model. Working directly in HTML, you always have to be aware of every tag.

        HTML

        <div class="tabs">
          <ul class="tabs__list">
            <li class="tabs__item">
              Tab 1
            </li>
            <li class="tabs__item">
              Tab 2
            </li>
          </ul>
          <div class="tabs__panel">
            <p>
              Tab 1 content
            </p>
          </div>
          <div class="tabs__panel">
            <p>
              Tab 2 content
            </p>
          </div>
        </div>

        JSX

        <Tabs>
          <div label="Tab 1">
            Tab 1 content
          </div>
          <div label="Tab 2">
            Tab 2 content
          </div>
        </Tabs>

        ? One of these probably looks preferrable, depending on your point of view.

        Writing code closer to the metal means more direct control, but also more tedium. Using a framework like React means you get more functionality “for free,” but also it can be a black box.

        That is, unless you understand the underlying nuances already. Then you can fluidly operate in either realm. Because you can see The Matrix for what it really is: Just JavaScript™. Not a bad place to be, no matter where you find yourself.

        The post The Anatomy of a Tablist Component in Vanilla JavaScript Versus React appeared first on CSS-Tricks.

        Categories: Designing, Others Tags:
  • List Style Recipes

    May 5th, 2020 No comments

    Lists are a fundamental part of HTML! They are useful in things like blog posts for listing out steps, recipes for listing ingredients, or items in a navigation menu. Not only are they an opportunity for styling, but they have accessibility implications. For example, the number of items in a list is announced in a screen reader to give some context to the list.

    Let’s focus on styling lists here, mostly just ordered and unordered lists (with apologies for snubbing our friend the definition list), and somewhat unusual styling situations.

    The Basics

    Before you do anything too fancy, know that there is quite few settings for list-style-type that might cover your needs out of the gate.

    CodePen Embed Fallback

    The Break in the Middle

    Ordered lists can start at any number you want them to.

    CodePen Embed Fallback

    The Nested Decimals

    CodePen Embed Fallback

    The Reversed Top 10 List

    A single reversed attribute will do the trick.

    CodePen Embed Fallback

    Image Bullets

    The best bet is using a background-image on a pseudo-element. You’d think list-style-image would be the way to go, but it’s extremely limited. For example, you can’t position it or even resize it.

    CodePen Embed Fallback

    Emoji Bullets

    CodePen Embed Fallback

    Hand-Picked “Random” Order

    The value attribute will set a list item to use the marker relevant for that position.

    CodePen Embed Fallback

    Custom Text Counters

    Can be done with pseudo-elements for the most control, but there is also list-style-type: '-';

    CodePen Embed Fallback

    Inside vs. Outside

    Things line up nicer with list-style-position: outside; (the default value), but the list markers render outside the box, so you have to be careful not to cut them off. They could hang off the edge of the browser window, or overflow: hidden; will hide them completely. The last two examples here have a trick to mimic the nicer alignment while rendering inside the element.

    CodePen Embed Fallback

    Colored Bullets

    Three ways here:

    1. ::marker (newest and easiest)
    2. Classic pseudo-element style
    3. background-image (this one is an SVG Data URL so you can manipulate the color from the CSS)
    CodePen Embed Fallback

    Columized List

    The number of columns can be automatic.

    CodePen Embed Fallback

    Colored Circle Numbers

    CodePen Embed Fallback

    Custom Cycling List Symbols

    One-offs can be done with list-style: symbols() and reusable sets can be made with @counter-style then used. Note this is only supported in Firefox at the time of writing.

    CodePen Embed Fallback

    The post List Style Recipes appeared first on CSS-Tricks.

    Categories: Designing, Others Tags:

    Angular + Jamstack! (Free Webinar)

    May 5th, 2020 No comments

    (This is a sponsored post.)

    It’s easy to think that working with Jamstack means working with some specific set of technologies. That’s how it’s traditionally been packaged for us. Think LAMP stack, where Linux, Apache, MySQL and PHP are explicit tools and languages. or MEAN or MERN or whatever. With Jamstack, the original JAM meant JavaScript, APIs, and Markup. That’s not specific technologies so much as a loose philosophy.

    That’s cool, because it means we can bring our own set of favorite technologies, and then figure out how to use that philosophy for the most benefit. That can mean bringing our favorite CMS, favorite build tools, and even favorite front-end frameworks.

    That’s the crux of Netlify’s upcoming webinar on using Angular in the Jamstack. They’ll walk through where Angular fits into the Jamstack architecture, how to develop with Angular in the stack, and the benefits of working this way. Plus you get to hang with Tara Z. Manicsic, which is worth it right there.

    The webinar is free and scheduled for May 13 at 9:00am Pacific Time.

    Direct Link to ArticlePermalink

    The post Angular + Jamstack! (Free Webinar) appeared first on CSS-Tricks.

    Categories: Designing, Others Tags:

    Meet SmashingConf Live: Our New Interactive Online Conference

    May 5th, 2020 No comments
    A Smashing Cat sitting comfortable in  a chair near a fireplace, with a cup of coffee, a dog nearby and a mouse in the back.

    Meet SmashingConf Live: Our New Interactive Online Conference

    Meet SmashingConf Live: Our New Interactive Online Conference

    Vitaly Friedman

    2020-05-05T12:50:00+00:002020-05-06T07:38:57+00:00

    In these strange times when everything is connected, it’s too easy to feel lonely and detached. Yes, everybody is just one message away, but there is always something in the way — deadlines to meet, Slack messages to reply, or urgent PRs to review. Connections need time and space to grow, just like learning, and conferences are a great way to find that time and that space.

    In fact, with SmashingConfs, we’ve always been trying to create such friendly and inclusive spaces. Places where like-minded folks could build lasting friendships, learn new skills and share their insights. These things often happen unexpectedly: when you run into an old friend, or spark a random conversation around a coffee machine. Well, in a physical world, that is.

    Online is different. We often think that it’s a bad thing, but it doesn’t have to be. For SmashingConf Live, our new online conference, we don’t want to replicate in-person experiences online. Instead, we’ve designed an entirely new conference experience, focusing around interactive live sessions and discussion zones where you actively shape what happens next, and learn along the way. Jump to speakers & topics.


    A Smashing Cat sitting comfortable in  a chair near a fireplace, with a cup of coffee, a dog nearby and a mouse in the back.

    Conf + Workshop

    $ 425.00 $ 675.00

    Conference ticket and an online workshop of your choice (extra 5 × 2.5h sessions).

    Conf Only

    $ 175.00 $ 225.00

    Two half-days, with interactive live sessions and discussions with experts. Only 100 early-birds.

    What’s SmashingConf Live Like?

    With SmashingConf Live (June 9–10), we aim to bring all the valuable bits and pieces of a conference to your fingertips. That means practical, hands-on sessions on front-end & UX, easy access to speakers and a friendly, approachable community.

    We know that staying in front of the screen for a long time is difficult, so instead of two full days, we break the entire conference into two half days, with enough time for a short break, friendly chats, coffee refills and checking in with your family. Plus enough time to double-check the slides and video recordings at your own time and pace.

    SmashingConf has always been a single-track experience, and SmashingConf Live is no exception. However, during the breaks you can jump between plenty of activities in discussion zones. You might meet old and new friends there, and we’ve also invited experts to run short sessions and answer your questions. So if you spot somebody familiar, that might not be a coincidence!



    That’s just a start though. In interactive, live sessions you take the driver’s seat, shaping the direction of the sessions as they are happening. You bring up topics and challenges that you’d love to address, and we’ll promote them on stage, so experts (and the entire community) can answer them either live or in the Q&A afterwards.

    Not enough? You can also wander between rooms with mystery guests, drop by a front-end & UX job board, crack design & coding challenges, and ask for a live audit of your site or app. We provide live captioning in English as well. That’s SmashingConf Live.

    Conf + Workshop

    $ 425.00 $ 675.00

    Conference ticket and an online workshop of your choice (extra 5 × 2.5h sessions).

    Conf Only

    $ 175.00 $ 225.00

    Two half-days, with interactive live sessions and discussions with experts. Only 100 early-birds.

    What Do You Get From It?

    So what is SmashingConf Live in a nutshell?

    • A friendly online conference.
      Focused on practical insights in front-end & UX. June 9–10, two half-days, each 4.5h long. Check the timing.
    • A diverse, kind global community.
      We embrace our code of conduct.
    • Interactive, live sessions.
      You take a driver’s seat and shape sessions with speakers, live.
    • Single-track with discussion zones (details).
      Attend mini-sessions with experts during breaks.
    • Easy access to experts.
      Ask your questions during the Q&A and in discussion zones.
    • All video recordings and slides.
      Rewatch a session if you missed it.
    • Collaborative note-taking (details).
      For everyone to put together notes in a single place.
    • Live site/app audits and challenges.
      Ask experts for a live audit of your project.
    • Online workshops before & after the event (details).
      Accessibility, performance, front-end and UX.
    • Live captioning.
      You can always turn on subtitles to follow along.
    • Get early-birds-tickets!
      Save $50 off the regular price. Only 100 tickets are available.

    Approachable, Friendly Speakers

    With every session, we want you to take away insights that you can apply to your work immediately. That’s why all sessions are designed to be highly practical — be it techniques to improve your workflow, craft a better front-end, or architecting a better design system.


    We invite speakers who have stories to tell and insights to share — and can make you feel a part of the performance, too.

    First confirmed speakers and topics are:

    • Sarah Drasner (Netlify)
      JavaScript, Vue.js
    • Josh Clark (Big Medium)
      Machine Learning, UX
    • Brad Frost (Atomic Design)
      Design Systems, Workflow
    • Chris Coyier (CSS Tricks)
      Front-End
    • Dan Mall (SuperFriendly)
      Design, Workflow
    • Nadieh Bremer
      Data Visualization
    • Harry Roberts (CSSWizardry)
      Web Performance
    • More sessions on accessibility, CSS/JavaScript, design & UX, HTML email, internationalization and many others.

    We’re happy to welcome our truly smashing hosts Cassidy Williams and Phil Hawksworth to MC the conference. And you’ll find Rachel Andrew, Paul Boag, Monica Dinculescu, Martin Splitt along with dozens of other experts in discussion zones. More speakers and guests will be announced soon!

    All speakers are friendly and approachable, so please don’t hesitate to bring up topics or issues that are most relevant to you. Speakers will also be available in breakout rooms, so you can always ask questions before and after their sessions.

    <!–

    Interactive, Live Sessions With You

    We didn’t want to have pre-recorded sessions, and instead looked into creating a truly engaging, interactive experience. We’ve encouraged speakers to find their own way of interacting with the audience, and mix lecture with interactive bits.

    You can help speakers evolve the session as it happens, and bring up topics that are most relevant to you. So every session is unique, because you actually shape it with the speaker on stage — that’s also why we call it Live.

    –>

    Discussion Zones With Experts

    Apart from speakers, we’ve also invited well-respected experts to join the discussion zones with Q&A and short workshops. That’s your opportunity to ask pretty much anything, with speakers sharing their screen and answering your questions in detail.

    In each discussion zone, we’ll have changing topics and moderators for each day, exploring obscure techniques, strategies, tooling and whatever questions and challenges you bring up!

    <!–

    How Does It Work?

    For the conference experience, we’re using Hopin. It turned out to be the best option in terms of quality, reliability and accessibility, with reception and networking area, sponsor booths and breakout rooms.



    For the conference, we’ll be using Hopin, an in-browser solution with the main stage, discussion rooms, video stream and a friendly chat. No installation required!

    No installation needed: jump right into the conference with a click on a link. Just make sure to say hello in the chat to all the friendly people around once you’ve joined! ?????

    –>

    Timings Around The World

    • CityTime
    • San Francisco, USA ??8AM–2PM
    • New York, USA ??11AM–4PM
    • Rio de Janeiro, Brazil ??12PM–5PM
    • London, UK ??4PM–9PM
    • Antwerp, Belgium ?5PM–10PM
    • Kyiv, Ukraine ??6PM–11PM
    • Mumbai, India ??8:30PM–1:30AM
    • Sydney, Australia ??11PM–5AM

    Collaborative Note-Taking

    Many attendees take notes of the main takeaways from each session. Yet sometimes these notes miss important URLs or key details. With collaborative Google docs, attendees can take notes, thoughts, resources, tips and questions together, while also documenting answers or adding relevant details to the doc.


    You might not be able to take all notes on time, but no worries: the community is here to support you, with a collaborative Google Doc.

    It’s magical to see people correcting each other’s typos and helping in those docs, or sometimes even resolving discussion within a matter of minutes if somebody spotted an error or a wrong URL. As a result, by the end of the session, you’ll have a comprehensive report of all insights from the session that you can bring back to your team right away.

    Chat and Connect In Slack

    Building connections matters, and while a conference is often a great place to kindle conversations, they can easily fade away just a few days after the event. We’ll use a Slack channel for you to connect before, during and after the conference. So you can jump between rooms being sure that you stay in touch with your friends. Won’t be that easy to get lost after all!


    Meet a truly global audience to get together and learn from each other. We invite all opinions, and happy to discuss them on stage as well.

    <!–

    Slides and Recordings

    Inclusivity lies at the very heart of every SmashingConf. So for the SmashingConf Live, we’ll provide full captioning in English language, so you can always turn on subtitles to follow along.

    And no worries at all if you missed a session or two: you can always rewatch it later at your own time, at your own pace. We’ll collect all slides and video recordings and make them available to you as well.

    –><!–

    Meet Our Partners And Local Meet-Ups

    You can drop by our sponsors and partners to learn about their products and services, but also attend their demos and ask them questions. We’ve also invited local communities from around the world to present themselves during the break sessions, so you can connect with global and local communities as well.



    Drop by our partners, sponsors and meet-ups and make connections in no time!

    –>

    Meet Tobi, Our Conference DJ

    Of course the conference is all about learning and connecting, but a friendly and fun experience is a part of every SmashingConf, too. So you are likely to notice our conference DJ Tobi Baldower playing in the breaks. Tobi always pours his soul and heart into creating a truly memorable experience for everyone, so please drop by and say hello! 😉


    Our SmashingConf resident, DJ Tobi — truly smashing since… forever!

    Find All The Cats!

    Are you ready for a lil’ riddle? We’ll hide a few cats in different rooms at different times, so try your luck to spot all of them! Expect a scavenger hunt and a quiz trivia show!

    Also, we’ll be working on challenges and group projects together. If that’s not a great chance to meet attendees, learn along the way and win some prizes, what is?


    Let’s see if you can find all the cats, shall we?

    Community Matters ??

    With SmashingConf Live, we aim for a warm, authentic family atmosphere. We are very, very excited, ad we want to take on this journey with you, and see what a truly smashing conference experience could be like!

    We’d be happy to stumble upon you when mingling between or during the sessions! And perhaps meet old and new friends from all over the world in one place — that’s something not every in-person conference can achieve. We’re still working on the final bits and pieces, and we’ll have some exciting updates coming your way soon 😉

    Stay smashing, meow and thank you for your ongoing support, everyone!


    Meet SmashingConf Live (June 9–10), a friendly online conference right at your fingertips.

    Conf + Workshop

    $ 425.00 $ 675.00

    Conference ticket and an online workshop of your choice (extra 5 × 2.5h sessions).

    Conf Only

    $ 175.00 $ 225.00

    Two half-days, with interactive live sessions and discussions with experts. Only 100 early-birds.

    Categories: Others Tags:

    8 of the Top WP Multipurpose Themes That You Can Use

    May 5th, 2020 No comments

    More than a few multipurpose WordPress themes have become big sellers in recent years, and for a reason.

    Actually, for several reasons.

    One reason is they serve as excellent toolkits for web designers who have large and varied clienteles.

    The best multipurpose themes, like those presented here, are popular for other reasons as well.

    • They are uniformly responsive, SEO friendly, and flexible.
    • Most are either reasonably, or very easy to work with.
    • Most offer a host of design materials and options, including inspirational demos or concepts.

    Unless you have a project for which only a specialty theme could provide what you need, any one of the following multipurpose themes should serve you well.

    1. BeTheme – Responsive Multi-Purpose WordPress Theme

    In the multipurpose WordPress theme world, BeTheme is an all-star. Bigger doesn’t always mean better, but this, the biggest of all WordPress themes with its 40+ core features, including 500+ pre-built websites, brings plenty of quality tools to the table.

    BeTheme is in fact one of the top 3 WordPress themes in use today.

    • Be’s 500+ pre-built websites are customizable, responsive, and cover the entire range of industry sectors, key business niches, and all the popular website types and styles.
    • The other core features, including the Muffin Builder, Admin Panel, Layout Generator, shortcodes, and grid, header, footer, and other design options give Be’s users all the flexibility they need.
    • Since key UX features are incorporated into each pre-built website, using one not only helps get the website-building process off to a fast start, but often makes it possible to have a website up and running in as little as 4 hours.

    Click on the banner to visit BeTheme and make it a point to check out a few of the pre-built websites.

    2. Total WordPress Theme

    Sometimes a website building tool offers something extra; something new and different you can’t quite put your finger on – like a “wonderous” design experience.

    You can have a wonderous experience when you take advantage of Total’s case, using a building-block approach to create a website

    • Total’s 90+ drag and drop customizable website-building elements make the design process as smooth and easy as can be.
    • 500+ styling options and 40+ pre-made demos provide all the design flexibility you’re ever apt to need.
    • With respect to editing, the Visual Composer editor and Total’s Theme Customizer give you both frontend and backend editing capability.

    Total is also developer friendly. Click on the banner to find out more about the wonderous design experience that could be yours.

    3. Avada Theme

    When you’re trying to decide on which multipurpose theme to use and you’re having trouble doing so because they are looking too much alike, you can always use sales as a tiebreaker.

    Avada is the all-time best selling theme on the market. That says a lot of course, but there are a host of other good reasons for choosing this WordPress theme.

    Like:

    • 40+ one-click portable demos to get your website-building project up to speed;
    • total access to all the popular WordPress plugins;
    • a Core Fusion toolbox featuring $200 worth of website-building tools;
    • easy access to a wide range of page and design options.

    Avada is 100% responsive, optimized for speed, and WooCommerce compatible.

    Click on the banner to learn more.

    4. Kalium

    Ease of use can be an important factor in selecting a multipurpose theme. Kalium is exceptionally easy to use for novices and experts alike. It has the best rating that we ever seen from all the premium themes.

    • Kalium is compatible with all the popular WordPress plugins.
    • Elementor, WPBakery Page Builder, WooCommerce, and Slider Revolution, Layer Slider, Advanced Custom Fields PRO, WooCommerce Product Filter and other premium plugins come with the package for free.
    • Kalium is 100% responsive and SEO friendly.
    • Kalium is ideal for building blog and portfolio websites, it has 35,000 customers and offers first-class customer support.

    Visit the site to find out more.

    5. TheGem – Creative Multi-Purpose High-Performance WordPress Theme

    This multipurpose theme, the Swiss Army knife of web design tools, is ideal for web designers looking to build eCommerce or business websites, or magazine, blogging, and portfolio sites.

    Key features include:

    • Highly customizable and performant WordPress theme, perfect for professionals and beginners
    • 400+ beautiful pre-built websites and templates for any purpose and niche.
    • TheGem Blocks: a selection of more than 300 section templates that can make website building a simple, straightforward process.
    • a unique collection of WooCommerce templates accompanied by the WPBakery page builder.

    Click on the banner to find out more.

    6. Uncode – Creative Multiuse WordPress Theme

    Uncode is the pixel-perfect solution for agencies, businesses, bloggers, and creative types in need of creating an attention-getting portfolio site.

    • The user-built website showcase demonstrates that if you can think of it, you can build it.
    • Uncode’s 70 pixel-perfect concepts are not only useable, but inspirational as well.
    • A powerful Frontend Editor together with more than 400+ Wireframes can serve to streamline website design workflows.

    Click on the banner to see for yourself everything Uncode has to offer.

    7. Movedo Premium WordPress Theme

    Ease of use can be a strong selling point. That happens to be the case with most premium multipurpose themes. Movedo takes things a step further. Movedo makes website-building fun. Try incorporating these cool design elements in your website and see for yourself.

    • Unique animations
    • Dynamic parallax effects
    • Sophisticated scrolling

    Click on the banner to learn more about Movedo and why it rocks!

    8. Hongo – Modern & Multipurpose WooCommerce WordPress Theme

    Hongo was developed with businesses, bloggers and WooCommerce stores in mind. This theme utilizes WPBakery custom shortcodes and WordPress’s Customizer to give you all the flexibility and customizability you’ll ever be likely to need.

    Hongo’s features include:

    • 11 ready one-click importable store demos;
    • a cool selection of 250 templates;
    • 200+ creative elements;
    • detailed online documentation.

    Hongo has a number of other helpful features as well. Click on the banner to learn more about this premier multipurpose theme.

    Multipurpose WordPress themes give users what they need in terms of tools and flexibility to create websites of any style and use. The only problem you’ll likely encounter trying to zero in on a multipurpose theme that will fully address your unique or special needs is when you’ve reviewed so many they begin to look alike.

    Since it can take valuable time to find what’s right for you given the huge number of multipurpose themes on the market, we hope to make your search easier by bringing this selection of premium multipurpose themes to your attention.

    [– This is a sponsored post on behalf of BAW Media –]

    Source

    Categories: Designing, Others Tags:

    Why Employees Are Your Greatest Asset in Preventing Phishing Attacks – [Infographic]

    May 5th, 2020 No comments

    Phishing attacks are on the rise and have more than doubled from 2013-2018. In 2018, 64% of businesses experienced a phishing attack – costing nearly $2 million per incident. 1 in 3 consumers will stop supporting a business after they’ve undergone a security breach, and 74% of hackers say they’re rarely impressed by an organization’s security measures. Knowing this, it’s urgent for businesses to improve their security tactics. The best route in doing so is through your employees.

    Why You Need People, Not Just Firewalls

    Your employees are the frontlines of your business. Most email communication will come through them before you. However, 72% of employees report that protecting themselves from email attacks have become increasingly difficult since 2016. Hackers have a niche in the psychology of phishing. The most common phishing email notifications employees fall for are:

    • Updated Building Evacuation Plans
    • Subject Lines Reading: Invoice Payment Required
    • Toll Violation Notifications

    Here’s why. Receiving an email notifying you with your company’s updated evacuation plans preys upon fear with need-to-know information. Receiving an email with an invoice mimics realistic personalized messages. Additionally, a toll violation notification creates a sense of urgency within the receiver.

    WIth 35% of employees unaware with “phishing” even means, there’s a dire need for additional protocols on top of annual email/cybersecurity training. 1 in 10 employees have clicked a link in a phishing email, and many employees forward the suspicious emails they receive to their IT department. Of all emails flagged by employees, just 15% are actually malicious, and many malicious emails fall through the cracks.

    A security breach decreases a company’s productivity by 67%, can cause them to lose 50% of their reputation and 54% of their data. This is why 95% of infosec professionals “recommend” training employees on how to identify phishing attacks in your annual training courses.

    People Learn Better Through Practice & Reinforcement

    More than half of Infosecurity professionals believe training has reduced phishing susceptibility. In fact, 76% of professional phishing victims receive additional counselling from a manager rather than negative consequences.

    The Key To Security Is People

    In 2018, 93% of security breaches involved phishing attacks. The internet is full of dark alleys, so it’s important to give your employees flashlights to see through its deception. Here are some ways you can loop your employees into your security tactics.

    • Train employees to spot phishing attacks
    • Give them feedback on their effectiveness
    • Give them tools that allow them to apply their training

    Phishing can go on to compromise 65% of the victim’s accounts, increase their malware infections by 49%, and cause them to lose 24% of their data. In 2018, 8 in 10 people experienced a phishing attack, 2 in 3 consumers received phishing emails, and 1 in 3 was compromised.

    An attack can go on to not only attack the victim’s data but also their personal life. Many of those who were compromised went on to have their social media or email account hacked. Why isn’t looking out for red flags enough?

    It’s Tough To Spot a Fake

    There’s a reason phishing attacks have more than doubled from 2013-2018. It’s tough to spot which emails are real, and which emails you should avoid clicking on. Not all phishing attempts make their way to your spam folder.

    Today, hackers are getting better at their job. 49% of hackers prefer to exploit human nature (emotions) rather than technology. By doing so, they can do a better job at manipulating the receiver into opening their messages and clicking on malicious links.

    In 2018 alone, 83% of people received phishing emails. Time is money – and emails are, too. What are you doing to increase your business’ security? Remember: your employees are your best shield.


    Original Source: https://www.phishcloud.com/getting-out-of-the-phish-net/

    Categories: Others Tags:

    5 Ways to Grow Sales During COVID-19

    May 5th, 2020 No comments

    We are living in unprecedented times. The global pandemic has had a major impact on our lives and our economy.

    In addition to the vast personal losses across the country and across the world, so many businesses have been negatively impacted. According to CNBC, retail sales fell by 8.7% in March of 2020, the biggest drop since recording began.

    So can you hope to do more than stay afloat through this? Can you even grow sales now? These 5 tips to grow sales can help you build your business in tough times.

    Top Tips

    • Do DIY PR
    • Contribute Quotes to High-Traffic Articles about COVID-19
    • Leverage FB Leads
    • Streamline Communication with Incoming Leads
    • Optimize eCommerce Checkout Flow

    1. Do DIY PR

    COVID-19 is stealing the show, but the rule of seven still applies. Potential customers need to hear about you at least seven times before they act.

    And as awesome as we’re sure your business is, it’s not going to promote itself. Very few companies can afford to hire a PR firm, and in my experience, most PR firms are a waste of time and usually don’t show ROI on your investment.

    Do-It-Yourself PR is what most mid-size businesses are turning to stay competitive and get featured in the press to bring in leads, customers and to sustain their revenues.

    There are a TON of different ways to do PR on your own and stand out from your competition, one of the easiest ways is to employ something called a Quora PR hack where you use Quora as a platform to strike up conversations with journalists.

    But we are jumping ahead of ourselves… the first step is to figure out who to pitch, that is why the journalist you’re going to pitch will be interested in your topic?

    Here are the two main steps for pitching press:

    Identify Your Targets

    First, find journalists or sources that will be a good fit for your business. That is – identify who is writing about topics you’re an expert in and reach out to them. Here are some ways to do so:

    • Find out where competitors are being mentioned and reach out to journalists covering them
    • Find out where your competitors get backlinks from and reach out to them
    • Find Slack Channels, LinkedIn Groups, Facebook Groups, subreddits, forums where blog owners, journalists and folks who control large lists/followings of your potential customers hang out

    Finding targets is an ongoing project. Think about how fast the world has changed in the past two months. If you already have a list of targets, now is the time to reevaluate and search for more.

    Create Your Media Pitch

    Your media pitch is the email you use to get the attention of the journalist. There are countless ways to create a pitch and they all apply to these unprecedented times.

    It’s a little tough to teach someone how to write amazing media pitch, we have a very thorough media pitch tutorial on our website with templates and resources but it’s something you just need to practice no matter how much you read about.

    Right now however, in April 2020, most of the 5000+ companies pitching press on JustReachOut– our PR platform are pitching a story at the intersection of what they do and COVID19.

    For example, we have a customer which has a Learning Management System, here is what they did:

    • They compiled information on how many people are currently learning at home
    • They created a snazzy graphic to help readers fully understand the impact.

    You can also use real examples of how your product is helping people struggling with distance learning.

    Here is another example of an amazing study created by the folks at Fractl:

    Visualizing “Stay at Home” Compliance Over Time – USA Worse Than Most – From Apple’s Mobility Data

    Once your pitch is complete, send your email, complete with a great subject line and a quick hook. Subject line is the single MOST IMPORTANT factor in the success of your pitching. It’s the doorway to your pitch. If it sucks they’ll never open the pitch. So spend some time on the subject line.

    Follow Up

    Don’t forget to follow up. Rise above the noise by checking back in. 90% of responses come from the two to three follow ups you send after the initial pitch.

    2. Contribute Quotes to High-Traffic Articles about COVID-19

    Another way to grow sales now is to contribute quotes to high-traffic articles about COVID-19. Since the virus is all anyone is talking about these days, that’s where you want to show up.

    You could use some of the methods listed above, but there is an easier way.

    You can connect with journalists and reporters if you become a source for articles with HARO. Sites like HARO connect reporters and journalists to sources. Journalists post requests for quotes or insights. Then potential sources—that’s you—reach out to them.

    So becoming a source can be just a few clicks away. HARO organizes requests by subject matter, so you can easily find what’s relevant to your customers.

    3. Leverage FB Leads

    Up your Facebook game! Right now, CPMs are crazy low. According to Statista, March 2020 CPM rates were just $0.81, down from $1.88 in November 2019. Check it out:

    CPM stands for “cost per mille,” an advertising term for cost per thousand. So, $1 per CPM means you’ll pay $1 every time your ad is seen 1,000 times. In other words, in November 2019, on average, you’d have paid $1.88 per thousand. Four months later? Eighty-one cents.

    Don’t think a strong Facebook presence can help you? Think again. In addition to impressions, Facebook can help you build authority, improve brand awareness, and increase clicks and sales.

    Right now, your ad can reach more people for much less money. So what are you waiting for? It’s time to get started with Facebook if you aren’t already or crank up your existing FB game.

    4. Streamline Communication with Incoming Leads

    You never want to squander an inbound lead. But right now, leads are even more precious.

    But with so much of your sales team suddenly working from home it can be difficult to stay on top of everything. You definitely don’t want leads to disappear into unchecked email boxes or worse.

    Quite often what happens is that a lead might call you and email you and then two days later chat with you on the website. The person answering that chat two days later does not have a fast way to check to see if this person already spoke to an SDR or even contacted the company.

    Internal communications and record-keeping is not in the cloud and not centralized around a CRM, so sales teams and support team are not on the same page.

    A recent survey by Nextiva revealed that today’s employees use three devices, including their mobile device to communicate with their teams every day.

    Luckily, technology has your back, you just need to use it correctly! One of the simplest ways is to implement a communications suite which has something called unified communication. That is phone, chat, email, SMS messages, even customer support tickets are all integrated with a central CRM in the cloud so that everything is all in one place.

    This way anytime you’re chatting with a lead you have all their information right in front of you.

    5. Optimize eCommerce Checkout Flow

    ECommerce has been trending since it was invented. But with so many people around the world now confined to their houses, now is the time to capitalize on eCommerce.

    The advantage of eCommerce when potential clients are in lockdown is obvious. They can make purchases without breaking quarantine. Great, right?

    Well, there are some challenges for eCommerce businesses. In order to optimize your sales now, you need to address these challenges.

    Abandoned Carts

    Abandoned carts cost businesses $2-4 Trillion dollars every year. You know you can’t afford that right now. So it’s time to do what you can to close that sale.

    Some abandoned carts are from people who were just window shopping. You may be able to convince them to buy through targeted ads and reminder emails. However, a survey indicates that up to 40% aren’t going to buy anything.

    However, other top reasons buyers abandon carts include hidden fees, being required to create an account, high shipping costs, and site speed. As an online retailer, do everything you can to prevent these issues.

    Checkout UX

    User experience determines the success of your checkout process. So you must do everything you can to refine your UX to maximize sales.

    These days most people are familiar with the online checkout process. Make sure yours follows the now-familiar process of entering shipping information, billing information, and confirmation.

    Always lead them to the next step. Customers who don’t know what to do next can easily do nothing, and that means lost revenue for you.

    Plus, customers hate re-entering information. So don’t make them. If they have an account, auto-populate any information you already have. And make it easy to enter additional information.

    Conclusion

    If you can keep your company top of mind, leverage low ad prices, and optimize leads and eCommerce, you can stay competitive and even grow sales during COVID-19. This is good news for your team who needs their paychecks and good news for your customers who need the service you provide.

    And there are good lessons in here for after this is over. You can emerge stronger, more flexible, and ready for what’s next.

    Categories: Others Tags:

    Smashing Podcast Episode 15 With Phil Smith: How Can I Build An App In 10 Days?

    May 5th, 2020 No comments
    Photo of Phil Smith

    Smashing Podcast Episode 15 With Phil Smith: How Can I Build An App In 10 Days?

    Smashing Podcast Episode 15 With Phil Smith: How Can I Build An App In 10 Days?

    Drew McLellan

    2020-05-05T05:00:00+00:002020-05-05T13:07:56+00:00

    In this episode of the Smashing Podcast, we’re talking about building apps on a tight timeline. How can you quickly turn around a project to respond to an emerging situation like COVID-19? Drew McLellan talks to Phil Smith to find out.

    Show Notes

    Weekly Update

    Transcript

    Drew McLellan: He is director of the full-stack web development studio amillionmonkeys, where he partners with business owners and creative agencies to build digital products that make an impact. He’s worked on projects for the BBC, AirBnB, Sky Cinema, Pearson, ITV, and Sussex Wildlife Trust to name but a few and works right across the stack with React, Vue, Laravel, Gatsby and more. Hailing from Brighton on the UK South coast, he’s also an author for Smashing Magazine, writing recently about the Alpine JavaScript framework. So, we know he’s a skilled developer and communicator, but did you know he can solve a Rubik’s Cube in six seconds using only his feet? My Smashing friends, please welcome Phil Smith.

    Drew: Hi, Phil. How are you?

    Phil Smith: I’m smashing, Drew.

    Drew: We’re in the thick of this crisis of COVID-19 and I think one of the interesting ways that we’re placed as designers and developers and technologists is to be in this position where we can still work and we can still do our jobs. And the work that we do is often based around providing access to information or enabling people to communicate, which is, I think, very relevant in a situation like this. I was interested to look at how those skills could be put to use to help in a time of crisis and then I saw your blog post, Phil, about how you had been doing something just like that. What have you been working on? How did this all start?

    Phil: It’s a very crazy story. About three weeks ago I was catch up with some friends and we’re feeling very glum about the whole situation. We’ve got two kids who we’re trying to homeschool while keeping this business going. And I was feeling a bit down about not doing that very well and not doing my job very well and the prospects of seeing friends and things like that. And then I had a chat with my wife who said, “Look, you just need to pick yourself up a bit, really.” And the same day, a chap called David got in touch via Wired Sussex, which is a kind of tech group in and around Brighton. And he said he had a friend who’d built a website which was around flashcards for medical practitioners who are caring for patients suffering with COVID.

    Phil: He was looking for a developer to turn this website into an app and add a few features. And they wanted it done very, very quickly and they had essentially no money. And I dwelled on it for not very long and I’ve been building apps and have the experience of doing back-end and front-end development and it just felt like this was … It felt like a significant moment, really, where I was having a bit of a crisis and this incredible opportunity came round and this need and I could actually contribute something. So, I got in touch with David. There was a lot of back and forth. And then I spoke to Rachel, who is the founder of CardMedic. She’s currently in America, so there’s this weird time difference that we’ve got to deal with every day. But she was really keen and very trusting of me. I spoke to her husband who’s a bit more tech-savvy and then we set to work.

    Phil: Essentially, it was … There were a few features that she wanted added but this was really about actually building … The existing site is on Squarespace, so it needed a new back-end built and an API and then an app that calls on the API and a few nice features added. Yeah. People might have seen the app or they can download it. It’s ridiculously simple. It was really just about … It wasn’t about there’s loads of learning to be done, it was just about there’s quite a lot of work to do. I just need to get it done. I had a bit of client work to do but tried to put that off as much as possible and did a lot of late nights and got it churned out in about 10 days, I think it took, from starting to getting it on the App Store.

    Drew: Just in the briefest terms, what is the app and how do medics use it?

    Phil: One of the strange medical nuances of COVID is because of the way it’s grown, there are lots of people caring for patients who have COVID who have no experience in respiratory illness and they may well be looking after patients whose first language is their first language because of the rate that it’s grown and because of these issues like them dealing with it in care homes and things like that. What the app does is it’s a kind of flashcard system whereby if there’s a particular subject you need to speak to a patient about … That might be about something like someone’s having difficulty breathing … Then there would be a flashcard which explains to the patient why they’re having difficulty breathing and what the practitioner is going to do about that.

    Phil: The app can also read the script aloud and we’re currently in 10 languages, which is all machine-translated at the minute. Yeah. But that’s the basics of the app.

    Drew: That’s sounds incredibly important, incredibly useful for people working under this pressured situation out in the field. With the quick turnaround that was needed for this project for obvious reasons, how did you go about breaking it down and deciding what needed to be there for launch and what you could deal with and add later?

    Phil: Rachel had lots of feature requests that she wanted to add to the app. What we agreed from the outset was the first version, which is the version that is now available … The things that would ship are all the functionality on the existing site. That is translation into different languages, read-aloud, the text to speech, and then a list of cards alphabetically. And the one that we wanted to add, which we felt fitted into the things we wanted to launch with was a card where practitioners could take a photo of their face and then show it to their patients along with a kind of introductory text because you’ll be wear … A lot of these people are wearing PPE and they’re just losing … Caring for people and they don’t know what their faces look like.

    Phil: We agreed, actually, to get this thing to ship as soon as possible they are the only features that would make V1. And anything else, we’d park and then we’d prioritize. And we’re kind of going through that process now of saying, “Okay, what do we want to deal with next?” The interesting side of this has been that as we’ve shipped, actually lots of people have come forward with their own suggestions. And Rachel, who’s never done this before, is balancing the things that she thinks the app needs with the things that other people are saying the app needs and we’re trying to balance what is most important, here.

    Drew: It can be a great eye-opener, can’t it? Shipping early and then listening to feedback rather than spending a lot of time in development building loads and loads of features and then getting it in front of users.

    Phil: What’s been funny as well is when we got in the App Store last Friday … Yeah, about midnight on Friday and then The Guardian ran a piece on Saturday. Great. So, we got loads of coverage really quickly and there was quite a lot of feedback from people who aren’t practitioners and that is difficult for Rachel, I think, to deal with of, “Okay, where are these constructive ideas by practitioners and where are these interesting things but actually aren’t going to make a difference in this crisis?”

    Drew: This is a native mobile app that’s built largely with what we usually think of as web technologies. What was the stack and what was each part of that stack responsible for?

    Phil: Sit tight. Here we go. The first thing, I think, I did was I have an A3 pad and I mapped out data models and what I thought the data structure would look like. I then … I use a thing, and I don’t know how I ended up using it, but it’s called Apiary. I don’t even know you pronounce it. You know how you pronounce it.

    Drew: Might be Apurree?

    Phil: Yeah. One of those. I think Oracle bought it a few years ago, so I think it’s quite a big outfit now. Anyway, that allows you to write API documentation and it gives you a kind of mock API. I did that first of all. I think this is the first thing that I’ve ever done which is multilingual as well. Certainly, it’s the first API I’ve been multilingual so I had to do a bit of research and suss out … And part of the reason I decided with this, to do the documentation and the mock API, was just to play with a few ideas about how the API could be structured if it was multilingual.

    Phil: I settled on what I wanted the API to look like and then started to build a back-end using Laravel. I use Laravel for … I do both front-end and back-end. Everything back-end I do, I use Laravel. It’s just incredible. The speed at which you can build a proper back-end is just … And a really good back-end. It’s fast, it’s incredibly clever, what it does, and if it wasn’t for Laravel, I … I’m sure there are other things out there, maybe I’d learn Ruby or something, but it just allows me to get stuff done very quickly.

    Phil: For example, in the back-end you create one of these flashcards and then you send it off to get the audio transcription and to get it translated into other languages. And the APIs that we use for both those services are quite heavily throttled; you can only do so many requests a second. And the thought of having to deal with calling on other APIs and throttling requests and things like that … The thought of doing that without Laravel … I have no idea how I’d do it. But with Laravel, you read the documentation, hunt down a couple of tutorials and you’re away.

    Phil: The back-end was probably 90% done within three days, I’d say. I got all of that set up and then really turned my attention … There’s an admin interface whereby Rachel and others can go in and edit content and update content and add translations and get new audio files. But really, the primary purpose of the backend is the API. Once all the back-end was set up, I focused my attention on the app, which is entirely built using React Native. And that compiled down to both an IOS and Android app.

    Phil: Rachel doesn’t have an iPhone and I am completely in on the Apple ecosystem and partly for that reason, but partly because it’s just an amazing tool set, I’m using Expo, which is a collection of tools that wrap around React Native to help with speedy development. There’s an Expo app and what it allows you to do is when you’re in the development phase, completely bypass the App Store by just sending a JavaScript bundle to their servers and when users download the Expo Client on their phone, they can download that JavaScript bundle and load the app within the Expo Client. Does that make sense?

    Drew: It does, yeah.

    Phil: Yep. Expo was really the key thing in ensuring this app could be developed really swiftly because it meant every couple of hours I could build something and Rachel could be seeing it where the thought of doing a whole build and getting it to the App Store and go by the Google ecosystem, there’s no way you could do that every couple of hours. It just wouldn’t … You’d spend more time building than actually developing the app. Expo was crucial in that process.

    Drew: Expo’s the tool that you’re using as part of your development workflow to enable you to do that in the development phase but it’s not something you go into production with? Is that right?

    Phil: Exactly. It’s used in development phase but it also handles the build process. Using the CLI, it will build a package that you can then upload to the Play Store or to the App Store. It looks after all the authentication and keys and certificates and all the side of things which has traditionally been such a headache and incredibly daunting as well. And that has made … I think that has put a lot of people off app development. Getting all these certificates is so difficult and actually, Expo just makes that incredibly easy.

    Drew: How did you go about constructing things on the React side?

    Phil: I have a starter framework. I’ve developed a pattern of how I construct apps. I use Redux as state management and that, although it’s not prescriptive, there’s a rough structure that goes alongside that. Yeah, I don’t quite know how much detail to go into, but there’s a lot of stateless components at the end of it, which I’m getting into and I appreciate the advantages of that.

    Phil: One other thing that’s worth mentioning is I’m really getting into typing this year or trying to discipline myself to do it. I decided although it would take … I’m not great at it, so I knew it would take me longer to build the app with TypeScript but it felt a lot safer doing that because intelligence in my editor around TypeScript just meant that I wasn’t making mistakes as often. And I’ve fallen foul of that in the past where I’ve not used TypeScript and I’m getting lots of red screens where things are undefined and I’ve just avoided that and managed it. And that hopefully means now I can add features without risk of breaking stuff that is in there already.

    Drew: And have you done a lot of work with React Native before?

    Phil: Yep. I’ve built quite a few things in React Native. It’s nice now because it’s really settled down. And this goes with the whole react ecosystem now. Now I think hooks are being adopted a lot more widely and all those … That big latest batch of changes, everything feels like it’s settling a bit now and it’s worth learning those things and implementing them. Yeah, it’s great. It’s great.

    Drew: Just thinking about your workflow, you were saying you started with mocking up an API at the back-end. You then built a Laravel app to … The API was what your Laravel app was exposing to the mobile app, is that right?

    Phil: Exactly. Really, the documentation and the mock API was just to give me a standard to work toward. That is what I wanted to get to. And I also … I sometimes find that, actually, I’d quite like to work on the app now and not on the back-end and that allowed me to switch to work on the app when the back-end wasn’t in place. So, that was another reason for doing that.

    Drew: And I suppose that’s a workflow that larger teams could use and could lean on where you might have different people developing the back-end and a mobile app. If you have a mock API to start with, then both teams can work inwards towards that API at the same time.

    Phil: That’s how I first came across this idea because, actually, it meant that if I was building a back-end then someone else could develop the mobile app.

    Drew: How do you balance under time pressure? How do you create a balance between moving quickly and relying on technologies that you are familiar with and you know you can work quickly and you know that will do the job … How do you balance that between what might traditionally be a longer R&D phase where you workout, actually, what is the really best technology for this job? Is it a case of just going with what you know will do a good job and you can ship quickly?

    Phil: That is a good question. I think as soon as the project was mentioned to me, I thought I know exactly how I’m going to build all of this. And if I didn’t have kids and I sat in a dark room, I think I could have probably turned it all around in about five days if I’d have been working on it solid, solid, solid because the requirements were very much in line with my experience of building apps. I’ve built similar kind of things where it calls on an API, stores the results in state and presents them. I’m now at a position where there’s some bits where I’m like, “Okay, I need to go back and refactor that.”

    Phil: Like I’ve spoke about typing tin, but actually the types can be quite loose in the app and that needs to be tightened up. And on the back-end, there aren’t many tests and now we’re starting to roll the back-end out because lots of people have come forward and said, “Actually, this is a great resource. I’d like to volunteer my services to translate this into my native language.” The back-ends being used by more people so I’m just thinking, hang on, I need a few more tests in here to make sure that nothing can break because there are people using this in production now.

    Phil: I think I answered your question. Essentially, there was no decision making. I just had to get it out there as quickly as possible.

    Drew: Did at any point you consider making this as a progressive web app?

    Phil: We did. Just before this all kicked off there came an announcement which I didn’t fully consume. There was some announcement which I read on Jeremy Keith’s blog which made me nervous about progressive web apps. I really love the technology and the idea behind it, but I just didn’t sense it was far enough along yet. And I don’t sense it’s in people’s psyche quite enough whereas telling people to go to the App Store and download the app, everyone knows how to do that. It just felt like the safest bet was to get the app done.

    Drew: I find sometimes that people are more familiar with the concept of an app than they are with the concept of a website.

    Phil: Yeah, my sense, as well, was it just felt too early to place all our eggs in one basket with the progressive web app. I’m sure it will get there. I really hope it will get there because it feels a much better solution to that, but I don’t think we’re quite there yet.

    Drew: You presumably build React projects for the web as well as React Native projects. Is this something that you could take that code base from React Native and move it to the web at some point in the future? How different are those two different environments?

    Phil: One of the interesting developments in React Native over the past few years is Nicholas Geiger built a package called React Native Web, which … How React Native works is it’s React and then you have different clients. And the traditional clients are IOS and Android but Nicholas Geiger’s built this package whereby one of the clients is web. So, you’re building a React Native app but it spits out HTML and JavaScript. And actually, I think I’m right in saying the Twitter website, I think, is built using React Native Web or one of the Twitter … I’m pretty sure the Twitter website is using React Native Web. And it’s really good. Unfortunately, one of the packages we use doesn’t transfile down to React Native Web.

    Phil: However, I think my job for next week is going to be to ditch that package so that we can use React Native Web. And the reason I want to use that is because the website is still currently powered by Squarespace but I would like to use Squarespace for all the marketing stuff but for the actual flashcards, I would like to be using exactly the same code base as a mobile app and call in on the same API so that we can have consistency across the board.

    Drew: I was going to ask, actually, how the website fits into this. The same functionality is potentially going to be available or already is available via the website?

    Phil: Some of the functionality is available on the website. That was actually built in View. On the website, we just inject some JavaScript and that was a lot easier to do with View because it’s just a load of script tags. There’s no transfiling, there’s no funny business, and it was just very quick. And I was very confident that I could get that working quite quickly. Yeah, the website is done like that but hopefully by this time next week we’ll have built that with React Native Web.

    Drew: You mentioned that the app needed to be multi-lingual and your flashcards are available in different languages. What was the process of doing that and making that possible?

    Phil: The Squarespace site uses a plug-in by a company called Weglot which I was quite impressed by, actually. You essentially set up a load of sub domains and point those sub domains at the Weglot server and that, then, fetches the corresponding page of the English translation and translates it on the fly. And it’s seemingly very reliable and they have said for this service they’re not going to charge anything. And they have got an API as well as that service that they offer to Squarespace. When a card is updated, we post all that data to Weglot along with a list of translations that are active and Weglot sends us back a translation. I think it is larger than a wrapper around Google Translate and a few other services.

    Phil: We’re really hopefully that a professional translation service are going to take this on. Yeah. I’ll probably post something about that on my blog this week and that will be on the CardMedic website. But yeah, a professional translation service have said they’ll do it and they’ll do 10 languages. And then we’ve had a load of other people come forward and say they’re really happy to translate it to their languages. So, I’m building this editor feature whereby people who are … Quite a few people have come forward from Hungary and they can see a list of articles that have yet to receive a Hungarian translation and they can just pick them off and once they’re done, we’ll be able to push those new languages live.

    Drew: And another API you mentioned that you made yourself was one for text to speech. How did that work?

    Phil: The website uses a service called SiteSpeaker. Again, I think this might be a wrapper around Google text to speech services, but you send them a string of text and the language the text is in and the voice that you want, because you can have different voices, and it sends you back an audio file. I think it dumps it on S3 or something, sends you back a URL. There’s been some tricky bits around that, around how particular characters are encoded, especially when you get to foreign languages. That gets really difficult. But I think that is working pretty well now.

    Drew: One of the things that you mentioned as part of the basic requirements for version one was the ability to search for a flashcard. How are you handling the search within the app? Is that happening in the client or does that happen back on the server?

    Phil: That happens in the client and is ridiculously simple. And I’m sure there’s a much better way of doing searches than seeing if one string is included in another string. I think, again, that might be developed because for example, if you’re searching for breathing almost every article on there comes up and it probably needs to be a bit more sophisticated. But at the minute, it’s doing the job.

    Drew: That’s how search always starts.

    Phil: Yeah.

    Drew: The simplest possible solution and then you work out from there when you find the problems.

    Phil: Yeah.

    Drew: The Laravel back-end, how is that hosted?

    Phil: That is on Digital Ocean. Again, Digital Ocean has launched a COVID relief program, so they have put a load of credit on our account to cover the cost of this, which is great. I don’t think we’re paying for any service and we’re using a lot of services on there. The server was built using Forge, which is a service built by the founder of Laravel, Taylor Otwell, which spins up new Digital Ocean droplets and services on S3 and a few other hosting packages. It does all the stuff, in my eyes, that a sys admin would do like scheduling and cron jobs and upgrading and deployment. It just makes it so simple. I’d be lost without that.

    Drew: It sounds like that architecture of this app is making a lot of use of external services and APIs, which is a nice modern way to go. Given more time to investigate different options, do you think it’s the sort of app that could have been built with a serverless approach?

    Phil: It could have. One of the funny things about it is it’s not very demanding on the server. The jobs that do need to be done, like going to the text to speech, that’s intensive process but we’re not actually doing that process. We’re just calling API and it’s somebody else’s problem. There’s quite a lot of request to the server, but we cache … Everyone’s getting the same content so we just cache the API and flush the cache once every hour, I think. So there isn’t actually a lot of load on the server. It is not the cheapest droplet but it’s not far off the cheapest droplet and it’s doing fine. It probably could have been serverless but, again, I think that ecosystem isn’t quite … Well, I don’t know enough about it to be able to churn that out in this amount of time.

    Drew: Would you have done anything differently, looking back at the project now, about the way the technology came together? The choices that you made? Would you have done anything differently if you could do it over?

    Phil: I wish we’d use React Native Web from the start. I kind of tried to do that after the fact and realized that actually, that was going to be really difficult. I wish I’d used React Native Web from the start and played closer attention to that. I don’t think I’d have changed anything on the back-end side of things. I wish I had more time to have done it. I feel there are some bits I could have done better. And I maybe wish I could have got a designer involved. A lot of it is from a UI framework, the app itself, and there are some screens which I’m less happy with than others. And the screen I’m least happy with is the one that The Guardian decided to feature on their homepage over the weekend, so that was a bit annoying.

    Drew: Once an app is ready, you think about getting it into the hands who need it. From a web project point of view, that’s just deploying to a server of a CDN. With Native apps, it’s a little bit more complex than that, isn’t it? You need to know about the App Stores, about developer accounts and all that sort of business. Is that something you’ve done a lot of before? And how did that process go?

    Phil: Expo handle a lot of the difficult technical side of that and the documentation on the Expo site is incredible. If you’re just getting into this and you’re thinking oh, yeah, I’m a front-end dev, I think I could build an app, then you should just dive into Expo and give it a whirl because even if you don’t ship, it will take you through the whole process and explains everything really clearly. And I don’t know how they do it, but their documentation, they always manage to keep up to date with the Play Store and the App Store. So when the UI changes in … What’s it called? App Store Connect … Then actually, the Expo documentation is updated, which just makes everything so much easier because you just follow their instructions and it all works great.

    Phil: One of the biggest stress and difficulty with the whole project came about getting approval in the App Stores. We shipped … We first submitted the app to the Apple App Store last Thursday. Yeah, last Thursday. So, eight days ago as we’re recording this. And it was pretty promptly rejected with a very, very stern rejection notice saying, “Don’t try this again.” And it pointed us to a document which they published but I had not read saying, “We’re only going to release COVID apps from registered companies.” And this is all on my developer account at the time. And my heart sank and I thought, “Oh God. I’ve spent a lot of time on this and this woman, Rachel, has spent loads of time on it and it’s not going to happen.”

    Phil: I then calmed down a bit and we rushed through her developer account for her company. Thankfully, she was … CardMedic is a registered trademarked company, so we rushed through a developer account, made the application on her account and it was approved straight away. Getting the Android app published has been the same process but drawn out over 10 days and they sent us a really harsh rejection notice, whereas Apple’s was like, “We don’t know who you are. You’re some bloke with a funny company name. Why on earth are you talking about COVID?” Which I kind of understand.

    Phil: The Google rejection notice was talking about profiteering from the pandemic and saying the app was insensitive and it was just very, very scary. And I was quite disheartened but I wrote a very firm appeal to their rejection and said, “Look, we’re reputable. We got a letter from a consultant at the hospital. The app was on The Guardian and The Beeb last weekend and has also featured on Government UK this week.” We sent the Play Store links to those articles and they approved the app this morning. But yeah, that had been the biggest stress of the project because you obviously can’t phone up Tim Cook and say, “Hey, where’s my app?” You just kind of … Especially with Google, you just submit the app and you can put in some supporting notes but there’s no dialogue. So, that was quite stressful but we’ve done it now. It’s in.

    Drew: You’ve managed to get the app developed in about 10 days and it sounds like the reception has been pretty good, being featured on news outlets and going down quite well with its potential users. What are the next steps? Where does it go from here?

    Phil: The next steps are getting the translations better. We really want to incorporate some features which will help people who have some kind of learning difficulty. I think I’d likely involve adding illustrations to particular cards. There’s this key card which shows your headshot and says, “Hi, my name is Phil. I’m a doctor at UCH,” and so forth. That page currently isn’t translated because, obviously, it’s unique to everyone. So I want to sort out how I’m going to do that without … Because we need to do that presuming that the person is offline when they’re viewing that screen, so that’s a little bit challenging but I’m sure I’ll sort that out. And then there’s a whole load more cards to add over the weekend as we hear of more use cases and more stories about it. So, we’re getting some new cards written, which will help in those scenarios and we’ll hopefully get those on the app soon.

    Drew: It sounds like very valuable work to be doing and people can, of course, find out more about the app by going to CardMedic.com. I’ve been learning about building apps rapidly. What have you been learning about lately, Phil?

    Phil: I have been learning about how to make the perfect pulled pork because it was my birthday this week and we’re having a virtual Zoom party tomorrow night, so I currently have two very large cuts of pork barbecuing and they’ve done five hours and they’ve got about 12 hours to go.

    Drew: That sounds delicious. If I wasn’t vegetarian …

    Phil: Yeah. The pulled halloumi isn’t quite as tasty, I’m afraid.

    Drew: If you, dear listener, would like to hear more from Phil, you can follow him on Twitter where he’s @MonkeyPhil and his personal blog is MonkeyPhil.co. You can find examples of his work and hire him to work on your projects at AMillionMonkeys.co.uk. Thanks for joining us today, Phil. Do you have any parting words?

    Phil: I think I’d really encourage people if they are front-end web developers to at least explore building apps in React Native. If you’ve got experience in React and you’re willing to read a lot of documentation, actually the process is nowhere near as daunting as you’d imagine.

    (il)
    Categories: Others Tags:

    Playing With (Fake) Container Queries With watched-box & resizeasaurus

    May 5th, 2020 No comments

    Heydon’s is a damn fantastic tool. It’s a custom element that essentially does container queries by way of class names that get added to the box based on size breakpoints that are calculated with ResizeObserver. It’s like a cleaner version of what Philip was talking about a few years ago.

    I’m sure I’d be happy using on production, as it’s lightweight, has no dependencies, and has a straightforward approach.

    For development, I had the idea of using Zach’s interesting little web component. It wraps elements in another box that is resizeable via CSS and labels it with the current width. That way you don’t have to fiddle with the entire browser window to resize things — any given element becomes resizable. Again, just for development and testing reasons.

    You’d wrap them together like…

    <resize-asaurus>
      <watched-box widthBreaks="320px, 600px">
        <div class="card">
          ... 
        </div>
      </watched-box>
    </resize-asaurus>

    Which allows you to write CSS at breakpoints like…

    .card {
       .w-gt-320px { }
       .w-gt-600px { } 
    }

    That’s surely not what the CSS syntax for container queries syntax will end up being, but it accomplishes the same thing with clear and understandable generated class names.

    Example!

    Live demo!

    CodePen Embed Fallback

    The post Playing With (Fake) Container Queries With watched-box & resizeasaurus appeared first on CSS-Tricks.

    Categories: Designing, Others Tags:

    A Complete Guide to CSS Functions

    May 4th, 2020 No comments
    Anatomy of a CSS declaration. Inside of a selector class called .selector there is a declaration of background-image: url(‘parchment.jpg'); Arrows label the property (background-image), the function (url), and the argument (parchment.jpg).

    Introduction

    In programming, functions are a named portion of code that performs a specific task. An example of this could be a function written in JavaScript called sayWoof():

    function sayWoof() {
      console.log("Woof!");
    }

    We can use this function later in our code, after we have defined our desired behavior. For this example, any time you type sayWoof() in your website or web app’s JavaScript it will print “Woof!” into the browser’s console.

    Functions can also use arguments, which are slots for things like numbers or bits of text that you can feed into the function’s logic to have it modify them. It works like this in JavaScript:

    function countDogs(amount) {
      console.log("There are " + amount + " dogs!");
    }

    Here, we have a function called countDogs() that has an argument called amount. When a number is provided for amount, it will take that number and add it to a pre-specified sentence. This lets us create sentences that tell us how many dogs we’ve counted.

    countDogs(3); // There are 3 dogs!
    countDogs(276); // There are 276 dogs!
    countDogs("many"); // There are many dogs!

    Some programming languages come with baked-in functions to help prevent you from having to reinvent the wheel for every new project. Typically, these functions are built to help make working with the main strengths and features of the language easier.

    Take libraries, for example. Libraries are collections of opinionated code made to help make development faster and easier, effectively just curated function collections — think FitVids.js for creating flexible video elements.

    Like any other programming language, CSS has functions. They can be inserted where you’d place a value, or in some cases, accompanying another value declaration. Some CSS functions even let you nest other functions within them!

    Basics of CSS Functions

    Unlike other programming languages, we cannot create our own functions in CSS, per se. That kind of logic is reserved for CSS selectors, which allow you to create powerful conditional styling rules.

    As opposed to other programming languages — where the output of a function typically invisibly affects other logic down the line — the output of CSS functions are visual in nature. This output is used to control both layout and presentation of content. For example:

    .has-orange-glow {
      filter: drop-shadow(0.25rem 0 0.75rem #ef9035);
    }

    The CSS filter function drop-shadow() uses the arguments we provide it to create an orange outer glow effect on whatever it is applied to.

    In the following demo, I have a JavaScript function named toggleOrangeGlow that toggles the application of the class .has-orange-glow on the CSS-Tricks logo. Combining this with a CSS transition, we’re able to create a cool glowing effect:

    CodePen Embed Fallback

    You may be familiar with some CSS functions, but the language has a surprisingly expansive list!

    Much like any other technology on the web, different CSS functions have different levels of browser support. Make sure you research and test to ensure your experience works for everyone, and use things like @supports to provide quality alternate experiences.

    Common CSS Functions

    url()

    url() allows you to link to other resources to load them. This can include images, fonts, and even other stylesheets. For performance reasons, it’s good practice to limit the things you load via url(), as each declaration is an additional HTTP request.

    CodePen Embed Fallback

    attr()

    This function allows us to reach into HTML, snag an attribute’s content, and feed it to the CSS content property. You’ll commonly see attr() used in print stylesheets, where it is used to show the URL of a link after its text. Another great application of this function is using it to show the alt description of an image if it fails to load.

    CodePen Embed Fallback

    calc()

    If there’s one function you should spend some time experimenting with, it’s calc(). This function takes two arguments and calculates a result from the operator (+, -, *, /) you supply it, provided those arguments are numbers with or without an accompanying unit.

    CodePen Embed Fallback

    Unlike CSS preprocessors such as Sass, calc() can mix units, meaning you can do things like subtract 6rem from 100%. calc() is also updated on the fly, so if that 100% represents a width, it’ll still work if that width changes. calc() can also accept CSS Custom Properties as arguments, allowing you an incredible degree of flexibility.

    lang()

    Including a lang attribute in your HTML is a really important thing to do. When present in your HTML, you’re able to use the lang() function to target the presence of the attribute’s value and conditionally apply styling based on it.

    One common use for this selector is to set language-specific quotes, which is great for things like internationalization.

    CodePen Embed Fallback

    Clever designers and developers might also use it as a hook for styling translated versions of their sites, where cultural and/or language considerations mean there’s different perceptions about things like negative space.

    :not()

    This pseudo class selector will select anything that isn’t what you specify. For example, you could target anything that isn’t an image with body:not(img). While this example is dangerously powerful, scoping :not() to more focused selectors such as BEM’s block class can give you a great deal of versatility.

    CodePen Embed Fallback

    Currently, :not() supports only one selector for its argument, but support for multiple comma-separated arguments (e.g. div:not(.this, .that)) is being worked on!

    CSS Custom Properties

    var() is used to reference a custom property declared earlier in the document. It is incredibly powerful when combined with calc().

    An example of this is declaring a custom property called --ratio: 1.618; in the root of the document, then invoking it later in our CSS to control line height: line-height: var(--ratio);.

    Here, var() is a set of instructions that tells the browser, “Go find the argument called --ratio declared earlier in the document, take its value, and apply it here.”

    Remember! calc() lets us dynamically adjust things on the fly, including the argument you supply via var().

    This allows us to create things like modular scale systems directly in CSS with just a few lines of code. If you change the value of --ratio, the whole modular scale system will update to match.

    In the following CodePen demo, I’ve done exactly just that. Change the value of --scale in the Pen’s CSS to a different number to see what I mean:

    CodePen Embed Fallback

    It’s also worth mentioning that JavaScript’s setProperty method can update custom properties in real time. This allows us to quickly and efficiently make dynamic changes to things that previously might have required a lot of complicated code to achieve.

    Color Functions

    Another common place you see CSS functions is when working with color.

    rgb() and rgba()

    These functions allow you to use numbers to describe the red (r), green (g), blue (b), and alpha (a) levels of a color. For example, a red color with a hex value of #fb1010 could also be described as rgba(251, 16, 16, 1). The red value, 251, is far higher than the green and blue values (16 and 16), as the color is mostly comprised of red information.

    The alpha value of 1 means that it is fully opaque, and won’t show anything behind what the color is applied to. If we change the alpha value to be 0.5, the color will be 50% transparent. If you use an rgb() function instead of rgba(), you don’t have to supply an alpha value. This creates terser code, but prevents you from using transparency.

    CodePen Embed Fallback

    hsl() and hsla()

    Similar to rgb() and rgba(), hsl() and hsla() are functions that allow you to describe color. Instead of using red, green, and blue, they use hue (h), saturation (s), and lightness (l).

    I prefer using hsla() over rgba() because its model of describing color works really well with systematized color systems. Each of the color level values for these functions can be CSS Custom Properties, allowing you to create powerful, dynamic code.

    CodePen Embed Fallback

    Syntax updates

    In the upcoming CSS Color Module Level 4 spec, we can ignore the a portion of rgba() and hsla(), as well as the commas. Now, spaces are used to separate the rgb and hsl arguments, with an optional / to indicate an alpha level.

    Pseudo Class Selectors

    These selectors use specialized argument notation that specifies patterns of what to select. This allows you to do things like select every other element, every fifth element, every third element after the seventh element, etc.

    Pseudo class selectors are incredibly versatile, yet often overlooked and under-appreciated. Many times, a thoughtful application of a few of these selectors can do the work of one or more node packages.

    :nth-child()

    nth-child() allows you to target one or more of the elements present in a group of elements that are on the same level in the Document Object Model (DOM) tree.

    In the right hands, :nth-child() is incredibly powerful. You can even solve fizzbuzz with it! If you’re looking for a good way to get started, Chris has a collection useful pseudo selector recipes.

    :nth-last-child()

    This pseudo class selector targets elements in a group of one or more elements that are on the same level in the DOM. It starts counting from the last element in the group and works backwards through the list of available DOM nodes.

    CodePen Embed Fallback

    :nth-last-of-type()

    This pseudo class selector can target an element in a group of elements of a similar type. Much like :nth-last-child(), it starts counting from the last element in the group. Unlike :nth-last-child, it will skip elements that don’t apply as it works backwards.

    CodePen Embed Fallback

    :nth-of-type()

    :nth-of-type() matches a specified collection of elements of a given type. For example, a declaration of img:nth-of-type(5) would target the fifth image on a page.

    CodePen Embed Fallback

    Animation Functions

    Animation is an important part of adding that certain je ne sais quoi to your website or web app. Just remember to put your users’ needs first and honor their animation preferences.

    Creating animations also requires controlling the state of things over time, so functions are a natural fit for making that happen.

    cubic-bezier()

    Instead of keyword values like ease, ease-in-out, or linear, you can use cubic-bezier() to create a custom timing function for your animation. While you can read about the math that powers cubic beziers, I think it’s much more fun to play around with making one instead.

    A custom cubic bezier curve created on cubic-bezier.com. There are also options to preview and compare your curve with CSS's ease, linear, ease-in, ease-out, and ease-in-out transitions.
    Lea Verou‘s cubic-bezier.com.

    path()

    This function is paired with the offset-path property. It allows you to “draw” a SVG path that other elements can be animated to follow.

    CodePen Embed Fallback

    Both Michelle Barker and Dan Wilson have published excellent articles that go into more detail about this approach to animation.

    steps()

    This relatively new function allows you to set the easing timing across an animation, which allows for a greater degree of control over what part of the animation occurs when. Dan Wilson has another excellent writeup of how it fits into the existing animation easing landscape.

    Sizing and Scaling Functions

    One common thing we do with animation is stretch and squash stuff. The following functions allow you to do exactly that. There is a catch, however: These CSS functions are a special subset, in that they can only work with the transform property.

    scaleX(), scaleY(), scaleZ(), scale3d(), and scale()

    Scaling functions let you increase or decrease the size of something along one or more axes. If you use scale3d() you can even do this in three dimensions!

    translateX(), translateY(), translateZ(), translate3d(), and translate()

    Translate functions let you reposition an element along one or more axes. Much like scale functions, you can also extend this manipulation into three dimensions.

    perspective()

    This function lets you adjust the appearance of an object to make it look like it is projecting up and out from its background.

    rotateX(), rotateY(), rotateZ(), rotate3d(), and rotate()

    Rotate functions let you swivel an element along one or more axes, much like grasping a ball and turning it around in your hand.

    skewX(), skewY(), and skew()

    Skew functions are a little different from scaling and rotation functions in that they apply a distortion effect relative to a single point. The amount of distortion is proportionate to the angle and distance declared, meaning that the further the effect continues in a direction the more pronounced it will be.

    Jorge Moreno also did us all a favor and made a great tool called CSS Transform Functions Visualizer. It allows you to adjust sizing and scaling in real time to better understand how all these functions work together:

    As responsible web professionals, we should be mindful of our users and the fact that they may not be using new or powerful hardware to view our content. Large and complicated animations may slow down the experience, or even cause the browser to crash in extreme scenarios.

    To prevent this, we can use techniques like will-change to prepare the browser for what’s in store, and the update media feature to remove animation on devices that do not support a fast refresh rate.

    Filter Functions

    CSS filter functions are another special subset of CSS functions, in that they can only work with the filter property. Filters are special effects applied to an element, mimicking functionality of graphics editing programs such as Photoshop.

    You can do some really wild things with CSS filter functions, stuff like recreating the effects you can apply to your posts on Instagram!

    brightness()

    This function adjusts how, um, bright something appears. Setting it to a low level will make it appear as if it has had a shadow cast over it. Setting it to a high level will blow it out, like an over-exposed photo.

    CodePen Embed Fallback

    blur()

    If you’re familiar with Photoshop’s Gaussian Blur filter, you know how blur() works. The more of this you apply, the more indistinct the thing you apply it to will look.

    CodePen Embed Fallback

    contrast()

    contrast() will adjust the degree of difference between the lightest and darkest parts of what is applied to.

    CodePen Embed Fallback

    grayscale()

    grayscale() removes the color information from what it is applied to. Remember that this isn’t an all-or-nothing affair! You can apply a partial grayscale effect to make something look weathered or washed out.

    CodePen Embed Fallback

    An interesting application of grayscale() could be lightly applying it to images when dark mode is enabled, to slightly diminish the overall vibrancy of color in a situation where the user may want less eye strain.

    invert()

    While invert() can be used to make something look like a photo negative, my favorite technique is to use it in a inverted colors media query to invert inverted images and video:

    @media (inverted-colors: inverted) {
      img,
      video {
        filter: invert(100%);
      }
    }

    This ensures that image and video content looks the way it should, regardless of a user’s expressed browsing mode preferences.

    opacity()

    This function controls how much of the background is visible through the element (and child elements) the function is applied to.

    CodePen Embed Fallback

    An element that has 0% opacity will be completely transparent, although it will still be present in the DOM. If you need to remove an object completely, use other techniques such as the hidden attribute.

    saturate()

    Applying this filter can enhance, or decrease the intensity of the color of what it is applied to. Enhancing an image’s saturation is a common technique photographers use to fix underexposed photos.

    CodePen Embed Fallback

    sepia()

    There are fancier ways to describe this, but realistically it’s a function that makes something look like it’s an old-timey photograph.

    CodePen Embed Fallback

    drop-shadow()

    A drop shadow is a visual effect applied to an object that makes it appear like it is hovering off of the page. There’s a bit of a trick here, in that CSS also allows you to apply drop shadow effects to text and elements. It’s also distinct from the box-shadow property is that it applies drop shadows to the shape of an element rather than the actual box of an element.

    Skilled designers and developers can take advantage of this to create complicated visual effects.

    CodePen Embed Fallback

    hue-rotate()

    When a class with a declaration containing hue-rotate() is applied to an element, each pixel used to draw that element will have it’s hue valued shifted by the amount you specify. hue-rotate()‘s effect is applied to each and every pixel it is applied to, so all colors will update relative to their hue value’s starting point.

    This can create a really psychedelic effect when applied to things that contain a lot of color information, such as photos.

    CodePen Embed Fallback

    SVG filters

    filter() also lets us import SVGs filters to use to create specialized visual effects. The topic is too complicated to really do it justice in this article — if you’re looking for a good starting point, I recommend “The Art Of SVG Filters And Why It Is Awesome” by Dirk Weber.

    The word “West!” rendered in a Wild West-style font, with layered teal drop shadows giving it a 3D effect. Behind it is a purple starburst pattern. Screenshot.
    This effect was created by skillful application of SVG filter effects.

    Gradient Functions

    Gradients are created when you transition one color to one or more other colors. They are workhorses of modern user interfaces — skilled designers and developers use them to lend an air of polish and sophistication to their work.

    Gradient functions allow you to specify a whole range of properties, including:

    • Color values,
    • The position on the gradient area where that color comes in,
    • What angle the gradient is positioned at.

    And yes, you guessed it: the colors we use in a gradient can be described using CSS color functions!

    linear-gradient() and repeating-linear-gradient()

    Linear gradients apply the color transformation in a straight line, from one point to another — this line can be set at an angle as well. In cases where there’s more area than gradient, using repeating-linear-gradient() will, er, repeat the gradient you described until all the available area has been filled.

    CodePen Embed Fallback

    radial-gradient() and repeating-radial-gradient()

    Radial gradients are a lot like linear gradients, only instead of a straight line, color transformations radiate outward from a center point. They’re oftentimes used to create a semitransparent screen to help separate a modal from the background it is placed over.

    CodePen Embed Fallback

    conic-gradient() and repeating-conical-gradient

    Conic gradients are different from radial gradients in that the color rotates around a circle. Because of this, we can do neat things like create donut charts. Unfortunately, support for conic gradients continues to be poor, so use them with caution.

    A teal and red donut chart set to 36%. To the right of the chart is a range slider, also set to 36%. Screenshot.
    An adjustable conic gradient donut chart made by Ana Tudor

    Grid Functions

    CSS Grid is a relatively new feature of the language. It allows us to efficiently create adaptive, robust layouts for multiple screen sizes.

    It’s worth acknowledging our roots. Before Grid, layout in CSS was largely a series of codified hacks to work with a language originally designed to format academic documents. Grid’s introduction is further acknowledgement that the language’s intent has changed.

    Modern CSS is an efficient, fault-tolerant language for controlling presentation and layout across a wide range of device form factors. Equipped with Grid and other properties like flexbox, we’re able to create layouts that would have been impossible to create in earlier iterations of CSS.

    Grid introduces the following CSS functions to help you use it.

    fit-content()

    This function “clamps” the size of grid rows or columns, letting you specify a maximum size a grid track can expand to. fit-content() accepts a range of values, but most notable among them are min-content and max-content. These values allow you to tie your layout to the content it contains. Impressive stuff!

    CodePen Embed Fallback

    minmax()

    minmax() allows you to set the minimum and maximum desired heights and widths of your grid rows and columns. This function can also use min-content and max-content, giving us a great deal of power and flexibility.

    CodePen Embed Fallback

    repeat()

    You can loop through patterns of grid column and rows using repeat(). This is great for two scenarios:

    1. When you do know how many rows or columns you need, but typing them out would be laborious. A good example of this would be constructing the grid for a calendar.
    2. When you don’t know how many rows or columns you need. Here, you can specify a template that the browser will honor as it propagates content into your layout.
    CodePen Embed Fallback

    Shape Functions

    Like filter() and transform(), shape CSS functions only work with one property: clip-path. This property is used to mask portions of something, allowing you to create all sorts of cool effects.

    circle()

    This function creates a circular shape for your mask, allowing you to specify its radius and position.

    CodePen Embed Fallback

    ellipse()

    Like circle(), ellipse() will draw a rounded shape, only instead of a perfect circle, ellipse() lets you construct an oblong mask.

    CodePen Embed Fallback

    inset()

    This function will mask out a rectangle inside of the element you apply it to.

    CodePen Embed Fallback

    polygon()

    With polygon(), you are able to specify an arbitrary number of points, allowing you to draw complicated shapes. polygon() also takes an optional fill-rule argument, which specifies which part of the shape is the inside part.

    CodePen Embed Fallback

    Miscellaneous Functions

    These are the un-categorizable CSS functions, things that don’t fit neatly elsewhere.

    element()

    Ever pointed a camera at its own video feed? That’s sort of what element() does. It allows you to specify the ID of another element to create an “image” of what that element looks like. You can then apply other CSS to that image, including stuff like CSS filters!

    It might take a bit to wrap your head around the concept — and it has some support concerns — but element() is a potentially very powerful in the right hands.

    Preethi Sam‘s “Using the Little-Known CSS element() Function to Create a Minimap Navigator” demonstrates how to use it to create a code minimap and is an excellent read.

    Here, she’s created a minimap for reading through a longform article:

    CodePen Embed Fallback

    image-set()

    This function allows you to specify a list of images for the browser to select for a background image, based on what it knows about the capabilities of its display and its connection speed. It is analogous to what you would do with the srcset property.

    ::slotted()

    This is a pseudo-element selector used to target elements that have been placed into a slot inside a HTML template. ::slotted() is intended to be used when working with Web Components, which are custom, developer-defined HTML elements.

    Not Ready for Prime Time

    Like any other living programming language, CSS includes features and functionality that are actively being worked on.

    These functions can sometimes be previewed using browsers that have access to the bleeding edge. Firefox Nightly and Chrome Canary are two such browsers. Other features and functionality are so new that they only exist in what is being actively discussed by the W3C.

    annotation()

    This function enables Alternate Annotation Forms, characters reserved for marking up things like notation and annotation. These characters typically will be outlined with a circle, square, or diamond shape.

    Not many typefaces contain Alternate Annotation Forms, so it’s good to check to see if the typeface you’re using includes them before trying to get annotation() to work. Tools such as Wakamai Fondue can help with that.

    he numbers 1 and 2 enclosed in hollow and solid-filled circles. Following them are the letters B and R enclosed in hollow and solid-filled squares. Screenshot.Stylistic Alternates.
    Examples of annotation glyphs from Jonathan Harrell‘s post, “Better Typography with Font Variants”

    counter() and counters()

    When you create an ordered list in HTML, the browser will automatically generate numbers for you and place them before your list item content. These pieces of browser-generated list content are called counters.

    By using a combination of the ::marker pseudo-element selector, the content property, and the counter() function, we can control the content and presentation of the counters on an ordered list. For browsers that don’t support counter() or counters() yet, you still get a decent experience due to the browser automatically falling back to its generated content:

    CodePen Embed Fallback

    For situations where you have nested ordered lists, the counters() function allows a child ordered list to access its parent. This allows us to control their content and presentation. If you want to learn more about the power of ::marker, counter(), and counters(), you can read “CSS Lists, Markers, And Counters” by Rachel Andrew.

    cross-fade()

    This function will allow you to blend one background image into one or more other background images. Its proposed syntax is similar to gradient functions, where you can specify the stops where images start and end.

    dir()

    This function allows you to flip the orientation of a language’s reading order. For English, that means a left-to-right (ltr) reading order gets turned into right-to-left (rtl). Only Firefox currently has support for dir(), but you can achieve the same effect in Chromium-based browsers by using an attribute selector such as [dir="rtl"].

    CodePen Embed Fallback

    env()

    env(), short for environment, allows you to create conditional logic that is triggered if the device’s User Agent matches up. It was popularized by the iPhone X as a method to work with its notch.

    That being said, device sniffing is a fallacious affair — you shouldn’t consider env() a way to cheat it. Instead, use it as intended: to make sure your design works for devices that impose unique hardware constraints on the viewport.

    has()

    has() is a relational pseudo-class that will target an element that contains another element, provided there is at least one match in the HTML source. An example of this is be a:has(> img), which tells the browser to target any link that contains an image.

    img) targets only links that contain images in a collection of links that contain either images or paragraphs.” srcset=”https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/l75sROCU.png?w=3635&ssl=1 3635w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/l75sROCU.png?resize=300%2C114&ssl=1 300w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/l75sROCU.png?resize=1024%2C390&ssl=1 1024w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/l75sROCU.png?resize=768%2C293&ssl=1 768w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/l75sROCU.png?resize=1536%2C586&ssl=1 1536w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/l75sROCU.png?resize=2048%2C781&ssl=1 2048w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/l75sROCU.png?resize=1000%2C381&ssl=1 1000w, https://i0.wp.com/css-tricks.com/wp-content/uploads/2020/04/l75sROCU.png?w=3000&ssl=1 3000w” sizes=”(min-width: 735px) 864px, 96vw”>

    Interestingly, has() is currently being proposed as CSS you can only write in JavaScript. If I were to wager a guess as to why this is, it is to scope the selector for performance reasons. With this approach has() is triggered only after the browser has been told to process conditional logic, and therefore query the state of things.

    image()

    This function will let you insert either a static image (referenced with url(), or draw one dynamically via gradients and element().

    Trigonometry functions

    These functions will allow us to perform more advanced mathematical operations:

    • Sine: sin()
    • Cosine: cos()
    • Tangent: tan()
    • Arccosine: acos()
    • Arcsine: asin()
    • Arctangent: atan()
    • Arctangent: atan2()
    • Square root: sqrt()
    • The square root of the sum of squares of its arguments: hypot()
    • Power: pow()

    I’m especially excited to see what people who are more clever than I am will do with these functions, especially for things like animation!

    clamp()

    When providing minimum, maximum, and preferred values as arguments, clamp() will honor the preferred value so long as it does not exceed the minimum and maximum boundaries.

    clamp() will allow us to author things like components whose size will scale along with the size of the viewport, but won’t shrink or grow past a specific size. This will be especially useful for creating CSS locks, where you can ensure a responsive type size will not get so small that it can’t be read.

    :host() and :host-context()

    To be honest, I’m a little hazy on the specifics of the jargon and mechanics that power the Shadow DOM. Here’s how the MDN describes host():

    The :host() CSS pseudo-class function selects the shadow host of the shadow DOM containing the CSS it is used inside (so you can select a custom element from inside its shadow DOM) — but only if the selector given as the function’s parameter matches the shadow host.

    And here’s what they have to say about :host-context():

    The :host-context() CSS pseudo-class function selects the shadow host of the shadow DOM containing the CSS it is used inside (so you can select a custom element from inside its shadow DOM) — but only if the selector given as the function’s parameter matches the shadow host’s ancestor(s) in the place it sits inside the DOM hierarchy.

    :is() and :where()

    :is() has had a bit of an identity crisis. Previously referred to as both matches() and vendor prefixed as :-webkit-any/:-moz-any, it now enjoys a standardized, agreed-upon name. It is a pseudo class selector that accepts a range of selectors as its argument.

    This allows an author to group and target a wide range of selectors in an efficient way. :where() is much like :is(), only it has a specificity of zero, while the specificity of :is() is set to the highest specificity in the provided selector list.

    :is() and :where() will allow us a good deal of flexibility about how we select things to style, especially for situations where you may not have as much control over the web site or web app’s stylesheet (e.g. third-party integrations, ads, etc.).

    #CSS

    :is() selector ?
    the successor to :any() and :matches()

    sneak peak into our talk, here’s a neat gif I made with XD showing what the :is() selector syntax can do. be excited for Chrome Dev Summit y’all!https://t.co/0r2CcUx9Hv pic.twitter.com/wSuGOsDLvZ

    — Adam Argyle (@argyleink) November 7, 2019

    max() and min()

    These functions allow you to select either the maximum or minimum value from a range of values you provide. Much like clamp(), these functions allow us to make things responsive up until a certain point.

    :nth-col() and :nth-last-col()

    These pseudo-classes will allow you to select one or a specified series columns in a CSS grid to apply styling to them. A good mental model for how these functions will work is how CSS pseudo class selectors operate. Unlike pseudo class selectors, :nth-col() and :nth-last-col() should be able to target implicit grid columns.

    symbols()

    This function allows you to specify a list of different kinds of characters to use for list bullets. Much like annotation(), you’ll want to make sure the typeface you use contains a glyph you want to use as a symbol before trying to get symbols() to work.

    Deprecated Functions

    Sometimes things just don’t work out the way you think they will. While deprecated CSS functions may still render in the browser for legacy support reasons, it isn’t recommended you use them going forward.

    matrix() and matrix3d()

    These functions were turned into more discrete sizing and scaling functions.

    rect()

    This function was part of the deprecated clip property. Use the clip-path property and its values instead.

    target-counter(), target-counters(), and target-text()

    These functions were intended to help work with fragment URLs for paged (printed) media. You can read more about them on the W3C’s CSS Generated Content for Paged Media Module documentation

    Typography

    The web is typography, so it makes sense to give your type the care and attention it deserves. While CSS provides some functions specifically designed to unlock the potential of your website or webapp’s chosen typefaces, it is advised to not use the following functions to access these advanced features.

    Instead, use lower-level syntax via font-feature-settings. You can figure out if the font you’re using supports these features by using a tool such as Wakamai Fondue.

    character-variant(), styleset(), and stylistic()

    Many typefaces made by professional foundries include alternate treatments for certain letters, or combinations of letters. One example use case is providing different variations of commonly-used letters for typefaces designed to look like handwriting, to help make it appear more natural-looking.

    Two examples of the sentence, “Easy Sunday morning & my fox. The first sentence does not have Stylistic Alternates enabled. The second sentence does, with the alternate characters (a, “un”, “m, “rn” g, &, m, f, and x) highlighted in green. Screenshot.
    Stylistic Alternates example by Tunghsiao Liu‘s “OpenType Features in CSS”

    Utilizing these functions activates these special alternate characters, provided they are present in the font’s glyph set.

    Unfortunately, it is not a standardized offering. Different typefaces will have different ranges of support, based on what the typographer chose to include. It would be wise to check to see if the font you’re using supports these special features before writing any code.

    format()

    When you are importing a font via the url() function, the format() function is an optional hint that lets you manually specify the font’s file format. If this hint is provided, the browser won’t download the font if it does not recognize the specified file format.

    @font-face {
      font-family: 'MyWebFont';
      src: url('mywebfont.woff2') format('woff2'), /* Cutting edge browsers */
           url('mywebfont.woff') format('woff'), /* Most modern Browsers */
           url('mywebfont.ttf') format('truetype'); /* Older Safari, Android, iOS */
    }
    leader()

    You know when you’re reading a menu at a restaurant and there’s a series of periods that help you figure out what price is attached to what menu item? Those are leaders.

    The W3C had plans for them with its CSS Generated Content for Paged Media Module, but it unfortunately seems like leader() never quite managed to take off. Fortunately, the W3C also provides an example of how to accomplish this effect using a clever application of the content property.

    local()

    local() allows you to specify a font installed locally, meaning it is present on the device. Local fonts either ship with the device, or can be manually installed.

    Betting on someone installing a font so things look the way you want them to is very risky! Because of this, it is recommended you don’t specify a local font that needs to be manually installed. Your site won’t look the way it is intended to, even moreso if you don’t specify a fallback font.

    @font-face {
      font-family: 'FeltTipPen';
      src: local('Felt Tip Pen Web'), /* Full font name */
           local('FeltTipPen-Regular'); /* Postscript name */
    }
    ornaments()

    Special dingbat characters can be enabled using this function. Be careful, as not all dingbat characters are properly coded in a way that will work well if a user does something like change the font, or use a specialized browsing mode.

    swash()

    Swashes are alternate visual treatments for letters that give them an extra-fancy flourish. They’re commonly found in italic and cursive-style typefaces.

    An example of a swash being applied to a script-style typeface. There's two versions of the phrase, “Fred And Ginger”. The first version doesn't have swashes activated. The second example does. In the second example, the letter F, and, and the letter G are highlighted to demonstrate them being activated. Screenshot.
    Swash example by Tunghsiao Liu‘s “OpenType Features in CSS”

    Why so many?

    CSS is maligned as frequently as it is misunderstood. The guiding thought to understanding why all these functions are made available to us is knowing that CSS isn’t prescriptive — not every website has to look like a Microsoft Word document.

    The technologies that power the web are designed in such a way that someone with enough interest can build whatever they want. It’s a powerful, revolutionary concept, a large part of why the web became so ubiquitous.

    The post A Complete Guide to CSS Functions appeared first on CSS-Tricks.

    Categories: Designing, Others Tags: