<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Ben Read</title>
    <description>The latest articles on Forem by Ben Read (@endymion1818).</description>
    <link>https://forem.com/endymion1818</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F6033%2F2216344.png</url>
      <title>Forem: Ben Read</title>
      <link>https://forem.com/endymion1818</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/endymion1818"/>
    <language>en</language>
    <item>
      <title>Testing Web Components</title>
      <dc:creator>Ben Read</dc:creator>
      <pubDate>Tue, 23 Apr 2024 00:00:00 +0000</pubDate>
      <link>https://forem.com/endymion1818/testing-web-components-ci5</link>
      <guid>https://forem.com/endymion1818/testing-web-components-ci5</guid>
      <description>&lt;p&gt;&lt;strong&gt;I've been taking a deep dive into Web Components recently: building a complex application. I have really enjoyed it. However, as with any project I want to deliver something that is proven to work by means of static, unit and end-to-end tests. I've run in to some issues with existing test suites.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When I deliver a new application I want it to be as well documented as possible so that any changes to it can be made as easily as possible, and by people who don't know the codebase as well as I would.&lt;/p&gt;

&lt;p&gt;To that end, making sure that what I hand over is reasonably tested is paramount to my own sense of professionalism. It's only this that gives me a strong sense of accomplishment because I know it stands a good chance of lasting a long time, and it'll be easy for other people with less prior knowledge to work with.&lt;/p&gt;

&lt;p&gt;When I deliver code based on Web Components, I want to deliver on that same level of quality. I know I'm leaning on newer browser-native technologies, but they're not that new.&lt;/p&gt;

&lt;p&gt;I was therefore very surprised to find no adequate way to provide unit tests for Web Components.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Testing Trophy
&lt;/h2&gt;

&lt;p&gt;I've decided to adopt a standardised set of tools across all of our codebases so that non-specialist developers can drop into any of them with the least amount of friction that include the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Static analysis via JSDoc&lt;/li&gt;
&lt;li&gt;Unit testing via Vitest&lt;/li&gt;
&lt;li&gt;E2E testing via Cypress&lt;/li&gt;
&lt;li&gt;Manual testing with Storybook&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most of these tools work really well, but one definitely doesn't...&lt;/p&gt;

&lt;h2&gt;
  
  
  Abstractions, abstractions everywhere
&lt;/h2&gt;

&lt;p&gt;I think one of the problems is that for a large proportion of people adopting Web Components, they're here after having had experience with another framework, and looking for something equivalent.&lt;/p&gt;

&lt;p&gt;That's definitely not true in my case. I was a little annoyed at the way React forces you to think about JavaScript, and I wanted to work more closely with the browser instead of seeming to force JavaScript into a certain pattern.&lt;/p&gt;

&lt;p&gt;Fundamentally that meant that I wanted to remove the abstractions of a framework.&lt;/p&gt;

&lt;p&gt;There have been well meaning attempts to make a framework out of Web Components. And I see why: If you're used to a framework, which a lot of people will be saying "here's another framework, but for Web Components".&lt;/p&gt;

&lt;p&gt;The good thing about those frameworks is that they try to make something look a little more familiar, even when it's not. I looked at Lit Element extensively, and it definitely feels more like Vue. And they've got their ecosystem of tools and testing libraries and tutorials, which certainly helps adoption.&lt;/p&gt;

&lt;p&gt;Again, though, that's not what I'm after.&lt;/p&gt;

&lt;p&gt;I am a little freaked out going back to classes instead of purely functional components like in React. However there are benefits; understanding inheritance much clearer when you're writing something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MyComponent extends HTMLElement {}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are other pros and cons, but I want to focus on the subject at hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cypress: A Hidden Option
&lt;/h2&gt;

&lt;p&gt;Ok well, not really hidden, but I did not realise I had to manually turn &lt;em&gt;on&lt;/em&gt; a setting to enable Cypress to recognise the shadow DOM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// in cypress.config.js

const config = defineConfig({
  // ...other config
  e2e: {
    includeShadowDom: true,
  },
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I can write tests that include shadow dom selectors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;describe("template spec", () =&amp;gt; {
  beforeEach(() =&amp;gt; {
    cy.visit("http://localhost:5173");
  });
  it("should play", () =&amp;gt; {
    cy.get("media-player#my-player")
      .shadow()
      .find('media-control-bar')
      .find("media-play-button").
      click();
    cy.get("media-player#my-player")
      .should("have.attr", "mediaplaying", "true");
  });
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Annoying if you don't know about it, but works fine once you do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storybook: Works fine
&lt;/h2&gt;

&lt;p&gt;I've found Storybook particularly useful for manually testing that each argument I pass to the component works properly. And since Storybook re-renders the component when you change an input, you can see the player reacting to the arguments you pass it.&lt;/p&gt;

&lt;p&gt;It is quite verbose. I have to pass my arguments around several times, but it does a good job once you've got those in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
// basic setup
export default {
  title: 'Media Embeds',
  // these allow controls to render for the arguments
  // here's the first time I'm declaring them
  argTypes: {
    videourl: { 
      control: 'text',
    },
    posterurl: {
      control: 'text',
    },
  }
};

// I found arguments needed to be destructured and
// passed explicitly into my component
// That's 2 and 3
const Template = ({
  videourl,
  posterurl
}) =&amp;gt; `
  &amp;lt;media-player 
    videourl="${videourl}"
    posterurl="${posterurl}"
  &amp;gt;&amp;lt;/media-player&amp;gt;
`;

export const Player = Template.bind({});

// Now actually declaring defaults for those arguments, 5th time

/**
 * @type {UserOptions}
 */
Player.args = {
  videourl: 'https://stream.mux.com/A3VXy02VoUinw01pwyomEO3bHnG4P32xzV7u1j1FSzjNg/high.mp4',
  posterurl: 'https://image.mux.com/A3VXy02VoUinw01pwyomEO3bHnG4P32xzV7u1j1FSzjNg/thumbnail.jpg',
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There could be a way to reduce this, hopefully I'll be able to investigate before I hand the project over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vitest .. you're on your own
&lt;/h2&gt;

&lt;p&gt;I shouldn't really say this is a Vitest problem. It would be the same if you were using Jest or anything else.&lt;/p&gt;

&lt;p&gt;The problem really lies with DOM mocking libraries. None of them allow you to extend the HTML Element.&lt;/p&gt;

&lt;p&gt;I have found a library of helpers that got me way further than I had with anything else.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.ficusjs.org/"&gt;Ficus&lt;/a&gt; is billed as a lightweight abstraction of Web Components. There's that word again. But they do have a range of other tools that have been helpful: paticularly a wrapper around JSDom that includes favourable setup for Web Components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { describe, it, beforeEach, expect } from 'vitest';
import { init } from '@ficusjs/testing'

import '../src/app.js';

describe("&amp;lt;media-player&amp;gt; ", () =&amp;gt; {
  beforeEach(() =&amp;gt; {
    init();
  });
  it("should render", async () =&amp;gt; {
    document.body.innerHTML = '&amp;lt;media-player&amp;gt;&amp;lt;/media-player&amp;gt;';
    expect(document.querySelector('media-player')).to.exist;
  });
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This still meant I have to mock a whole bunch of stuff to even get it to render. And this is as far as I have got. Not even the venerable, cross-framework testing-library &lt;a href="https://testing-library.com/docs/dom-testing-library/intro/"&gt;covers the Shadow Dom&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I do have a lot of stuff I can unit test with Vitest. But there's so much more I want to do with it.&lt;/p&gt;

&lt;p&gt;If you know anything that might help, please let me know.&lt;/p&gt;

&lt;p&gt;I've also created a GitHub repo with examples if you're coming across this and are looking for resources for testing Web Components:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/endymion1818/example-web-component-test-suite"&gt;https://github.com/endymion1818/example-web-component-test-suite&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;More than anything, it's been a liberating, fun experience to see how far the web has come in the last few years. I hope I get to work with Web Components for a long time to come.&lt;/p&gt;

&lt;p&gt;And I hope more developers can start using them too.&lt;/p&gt;





&lt;p&gt;Thanks for reading this article via RSS. Let me know what you think by &lt;a href="mailto:endymion1818@gmail.com?subject=re:Testing%20Web%20Components"&gt;sending me an email.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>Using Aria States To Toggle Tailwind Classes</title>
      <dc:creator>Ben Read</dc:creator>
      <pubDate>Mon, 04 Mar 2024 11:13:35 +0000</pubDate>
      <link>https://forem.com/endymion1818/using-aria-states-to-toggle-tailwind-classes-1lef</link>
      <guid>https://forem.com/endymion1818/using-aria-states-to-toggle-tailwind-classes-1lef</guid>
      <description>&lt;p&gt;I maintain an internal UI library for a number of large sites. It's got a number of JavaScript interactions for menus, search buttons and similar.&lt;/p&gt;

&lt;p&gt;In my first iteration of the project I used event handlers to add and remove classes directly in the code base, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;desktopSearchButton&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Show the search box&lt;/span&gt;
        &lt;span class="nx"&gt;desktopWrapper&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Add active state to button&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bg-blue-800&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rounded-tr-none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rounded-br-none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;desktopWrapper&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;desktopWrapper&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input[type=search]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apart from being verbose this is quite fragile code. I'm looking for a &lt;em&gt;style&lt;/em&gt; class and initializing an interactive state that is fixed inside the JS.&lt;/p&gt;

&lt;p&gt;This means if the style of the button changed, I would also have to change the JS. Also it could break the JavaScript if you weren't very careful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second iteration
&lt;/h2&gt;

&lt;p&gt;I started to refactor the code to instead use data attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;desktopSearchButton&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Show the search box&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;desktopWrapper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;desktopWrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;desktopWrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Add active state to button&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;desktopWrapper&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;desktopWrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input[type=search]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a lot more robust and easily re-useable. Now I can use &lt;a href="https://tailwindcss.com/docs/hover-focus-and-other-states#data-attributes"&gt;Tailwind arbitrary selectors&lt;/a&gt; in the template to toggle the states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;data-open=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hidden data-[open]:block"&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Third iteration
&lt;/h2&gt;

&lt;p&gt;With this one, I really wanted to make sure that accessibility was intrinsic to the project, and not an optional extra. To that end, instead of data attributes wherever possible I used &lt;a href="https://tailwindcss.com/docs/hover-focus-and-other-states#aria-states"&gt;aria states&lt;/a&gt; to toggle the visuals.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;desktopSearchButton&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Show the search box&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;desktopWrapper&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;desktopWrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ariaExpanded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;desktopWrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ariaExpanded&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Add active state to button&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ariaPressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ariaPressed&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;desktopWrapper&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;ariaExpanded&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;desktopWrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input[type=search]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;aria-pressed=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"hidden aria-pressed:block"&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only disadvantage here is just an inconvenience: they have to add the aria state to the DOM. And to be honest, that is a positive friction to make them more aware of how aria states work.&lt;/p&gt;

&lt;p&gt;Also my compiler complains when I try to make left-hand assignments with optional chaining:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;ariapressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="c1"&gt;// The left-hand side of an assignment expression may not be an optional property access.ts(2779)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I could escape this rule but instead opted for an &lt;code&gt;if&lt;/code&gt; statement rather than risk making my entire codebase less type safe.&lt;/p&gt;

&lt;p&gt;This way, any developers using my library would need to use aria states too, making it at least a bit better for all of our users.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>javascript</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>How to scope Tailwind CSS</title>
      <dc:creator>Ben Read</dc:creator>
      <pubDate>Fri, 23 Feb 2024 11:46:21 +0000</pubDate>
      <link>https://forem.com/endymion1818/how-to-scope-tailwind-css-3j57</link>
      <guid>https://forem.com/endymion1818/how-to-scope-tailwind-css-3j57</guid>
      <description>&lt;p&gt;I have a React app that uses Tailwind that gets embedded into a PHP app which also uses Tailwind. For that reason, I have two different builds of Tailwind that get injected into one page.&lt;/p&gt;

&lt;p&gt;You probably don't want to do this. But in my case it was unavoidable: the React app and the PHP app are totally separate. The React app gets injected into several different PHP projects to provide extra functionality that they don't provide, but it's also a fairly complex form that needs to fit in to the UI of my company and also look like it's part of the parent application. &lt;/p&gt;

&lt;p&gt;Both have a compilation step that is exclusive, so I could use the parent app's Tailwind CSS for some components, but I couldn't guarantee it will always contain what I need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Didn't work: prefixing Tailwind classes
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tailwindcss.com/docs/configuration#prefix"&gt;Tailwind itself provides a useful prefix utility&lt;/a&gt; that you would think scopes all of it's classes for you. However it doesn't do that. It's very useful if you're using Tailwind classes as well as other classes (say for separate JavaScript functionality).&lt;/p&gt;

&lt;h2&gt;
  
  
  Didn't work: Removing "preflight"
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tailwindcss.com/docs/preflight"&gt;Tailwind also has a sort of CSS reset which you can also leave out of your builds&lt;/a&gt;. Whilst this is very useful to know about, I still got leakage, plus some of my UI elements looked awful without it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked: Using css layers
&lt;/h2&gt;

&lt;p&gt;In my main CSS file I wrapped a CSS layer around my Tailwind imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@layer&lt;/span&gt; &lt;span class="n"&gt;myapp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;@layer&lt;/span&gt; &lt;span class="n"&gt;myapp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;utilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked. I was no longer seeing any styles removed in my parent application.&lt;/p&gt;

&lt;p&gt;A few times I had to use an &lt;code&gt;!important&lt;/code&gt; declaration to trump the reset, but that was fairly minor tradeoff and I think in the end I only used it on two classes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"!bg-sky-600 tw-m-2 !tw-p-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Horrible but tolerable.&lt;/p&gt;

&lt;p&gt;Hope this is helpful to someone!&lt;/p&gt;

</description>
      <category>css</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>A Collection of Tailwind UI Loading States</title>
      <dc:creator>Ben Read</dc:creator>
      <pubDate>Thu, 16 Nov 2023 22:48:55 +0000</pubDate>
      <link>https://forem.com/endymion1818/a-collection-of-tailwind-ui-loading-states-472h</link>
      <guid>https://forem.com/endymion1818/a-collection-of-tailwind-ui-loading-states-472h</guid>
      <description>&lt;p&gt;I made a collection of loading states for Tailwind. It was relatively easy exercise, and I'm surprised I didn't find anything like it out there already.&lt;/p&gt;

&lt;p&gt;You might recognise the first element from the &lt;a href="https://tailwindcss.com/docs/animation#pulse"&gt;Tailwind Docs&lt;/a&gt;, which demonstrates how to use the &lt;code&gt;animate-pulse&lt;/code&gt; class to make it look like it's in the process of loading in.&lt;/p&gt;

&lt;p&gt;Here's the CodePen:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/endymion1818/embed/eYbPBjE?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Feel free to copy or remix this pen, I'm not precious about it and just wanted a collection I could reference for some other work I was doing.&lt;/p&gt;

</description>
      <category>tailwindcss</category>
      <category>css</category>
    </item>
    <item>
      <title>Let's build a carousel with Tailwind and JavaScript!</title>
      <dc:creator>Ben Read</dc:creator>
      <pubDate>Wed, 21 Jun 2023 10:05:32 +0000</pubDate>
      <link>https://forem.com/endymion1818/lets-build-a-carousel-with-tailwind-and-javascript-5fpo</link>
      <guid>https://forem.com/endymion1818/lets-build-a-carousel-with-tailwind-and-javascript-5fpo</guid>
      <description>&lt;p&gt;Before building this, first we need to carefully consider whether a carousel is the best option for what we need to display. Carousels are very often not the solution you want: &lt;a href="https://shouldiuseacarousel.com/"&gt;please check out this site for some relevant statistics&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;If you have decided a carousel is best for your UI, then read on.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTML setup
&lt;/h2&gt;

&lt;p&gt;I'll start with explaining the HTML layout here. I'm using Tailwind because that is what I was using at the time, but you can use whatever CSS you like.&lt;/p&gt;

&lt;p&gt;First, we have an opening tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"animation-carousel"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-relative tw-w-full tw-h-36 md:tw-h-96 tw-my-8"&lt;/span&gt; &lt;span class="na"&gt;data-carousel&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things of note: We're going to use a lot of &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Positioning#absolute_positioning"&gt;relative and absolute positioning&lt;/a&gt; here. To make sure that is scoped to this container, we're making sure the position is &lt;code&gt;relative&lt;/code&gt;. And because each item is going to be positioned in this way, we're setting a specific &lt;code&gt;height&lt;/code&gt; too.&lt;/p&gt;

&lt;p&gt;Lastly, we're setting a &lt;code&gt;data&lt;/code&gt; attribute. This is what we're going to pick up in the JavaScript. I could just as easily have used a class name.&lt;/p&gt;

&lt;p&gt;Next, I've got the inner wrapper. This will contain the images, previous and next buttons, and indicators (the little navigation pips you sometimes see).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-relative tw-overflow-hidden tw-rounded-lg tw-h-36 md:tw-h-96"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Indicators
&lt;/h2&gt;

&lt;p&gt;Indicators are optional in this setup. If they're not in the DOM, we won't render them. To facilitate that, I've used a template tag so they don't render initially, rather, we can clone this template's inner HTML as many times as we need to render the pips:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt; &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"indicator-container"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-absolute tw-z-30 tw-flex tw-space-x-3 tw--translate-x-1/2 tw-bottom-5 tw-left-1/2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"carousel-indicator"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-w-3 tw-h-3 tw-rounded-full tw-border tw-border-white"&lt;/span&gt; &lt;span class="na"&gt;aria-current=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Slide 1"&lt;/span&gt; &lt;span class="na"&gt;data-carousel-slide-to=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 100 100"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-text-white"&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;circle&lt;/span&gt; &lt;span class="na"&gt;cx=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt; &lt;span class="na"&gt;cy=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt; &lt;span class="na"&gt;r=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt; &lt;span class="na"&gt;stroke=&lt;/span&gt;&lt;span class="s"&gt;"white"&lt;/span&gt; &lt;span class="na"&gt;stroke-width=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Template tags aren't rendered by the DOM tree but we can still select them with JavaScript. This is a handy feature as you'll see later. We've got a button with an SVG circle in there so we can click to select a certain slide.&lt;/p&gt;

&lt;p&gt;Each of these has an &lt;code&gt;aria&lt;/code&gt; label to indicate which item is focused on. I could have used a visually hidden text label.&lt;/p&gt;

&lt;p&gt;Next come the items themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Slides
&lt;/h2&gt;

&lt;p&gt;By default I want each of the slides to render stacked on top of each other; this is so that if there's a problem with the JavaScript, all parts of this UI element will still be visible to the user. We'll be hiding the slides and positioning them using JavaScript later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-h-36 md:tw-h-96"&lt;/span&gt; &lt;span class="na"&gt;data-carousel-item=&lt;/span&gt;&lt;span class="s"&gt;"active"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://placehold.co/600x400/orange/blue"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-absolute tw-block tw-w-full tw--translate-x-1/2 tw--translate-y-1/2 tw-top-1/2 tw-left-1/2 tw-h-36 md:tw-h-96"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-absolute tw-inset-0 tw-flex"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;blockquote&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-m-auto tw-z-40 tw-font-bold tw-text-4xl tw-max-w-xl tw-text-center tw-text-zinc-50"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        The
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-text-primary-400"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;more I learn&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;, the more I realise how much I don't know
        &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-mt-6 tw-text-sm tw-font-normal tw-italic"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          Albert Einstein
        &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/blockquote&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-hidden tw-h-36 md:tw-h-96"&lt;/span&gt; &lt;span class="na"&gt;data-carousel-item=&lt;/span&gt;&lt;span class="s"&gt;"active"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://placehold.co/600x400/orange/white"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-absolute tw-block tw-w-full tw--translate-x-1/2 tw--translate-y-1/2 tw-top-1/2 tw-left-1/2 tw-h-36 md:tw-h-96"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-hidden tw-h-36 md:tw-h-96"&lt;/span&gt; &lt;span class="na"&gt;data-carousel-item&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://placehold.co/600x400/red/white"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-absolute tw-block tw-w-full tw--translate-x-1/2 tw--translate-y-1/2 tw-top-1/2 tw-left-1/2 tw-h-36 md:tw-h-96"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you'll notice, as well as images, each slide can contain a text caption too. I could've used a figure and &lt;code&gt;figcaption&lt;/code&gt; element pair for this, but in my case, the image is purely decorative so I opted for a &lt;code&gt;blockquote&lt;/code&gt; for the quotation.&lt;/p&gt;

&lt;p&gt;We're going to be using the &lt;code&gt;data-carousel-item&lt;/code&gt; attribute again in the JavaScript, and attach a value of active to the current slide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Previous &amp;amp; Next Controls
&lt;/h2&gt;

&lt;p&gt;These controls render to the left and right of the slider and allow users to cycle through the images in order or reverse order. They have icons indicating the direction they're cycling in when they're clicked and a visually hidden label to aid assistive technology users.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-absolute tw-top-0 tw-left-0 tw-z-30 tw-flex tw-items-center tw-justify-center tw-h-full tw-px-4 tw-cursor-pointer tw-group focus:tw-outline-none"&lt;/span&gt; &lt;span class="na"&gt;data-carousel-prev&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-inline-flex tw-items-center tw-justify-center tw-w-8 tw-h-8 tw-rounded-full sm:tw-w-10 sm:tw-h-10 tw-bg-white/30 tw-dark:bg-gray-800/30 group-hover:tw-bg-white/50 dark:group-hover:tw-bg-gray-800/60 group-focus:tw-ring-4 group-focus:tw-ring-white dark:group-focus:tw-ring-gray-800/70 group-focus:tw-outline-none tw-transition-all tw-ease-in-out"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-w-5 tw-h-5 tw-text-zinc-400 hover:tw-text-zinc-800 sm:tw-w-6 sm:tw-h-6 tw-dark:text-gray-800 tw-transition-all tw-ease-in-out"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt; &lt;span class="na"&gt;stroke=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;stroke-linecap=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-linejoin=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-width=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M15 19l-7-7 7-7"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/path&amp;gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-sr-only"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Previous&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-absolute tw-top-0 tw-right-0 tw-z-30 tw-flex tw-items-center tw-justify-center tw-h-full tw-px-4 tw-cursor-pointer tw-group focus:tw-outline-none"&lt;/span&gt; &lt;span class="na"&gt;data-carousel-next&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-inline-flex tw-items-center tw-justify-center tw-w-8 tw-h-8 tw-rounded-full sm:tw-w-10 sm:tw-h-10 bg-white/30 dark:tw-bg-gray-800/30 group-hover:tw-bg-white/50 dark:tw-group-hover:tw-bg-gray-800/60 group-focus:tw-ring-4 group-focus:tw-ring-white dark:tw-group-focus:tw-ring-gray-800/70 group-focus:tw-outline-none tw-transition-all tw-ease-in-out"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-w-5 tw-h-5 tw-text-zinc-400 hover:tw-text-zinc-800 sm:tw-w-6 sm:tw-h-6 dark:tw-text-gray-800 tw-transition-all tw-ease-in-out"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt; &lt;span class="na"&gt;stroke=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;stroke-linecap=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-linejoin=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-width=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M9 5l7 7-7 7"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/path&amp;gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tw-sr-only"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Next&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the HTML, now the fun part.&lt;/p&gt;

&lt;h2&gt;
  
  
  JavaScript
&lt;/h2&gt;

&lt;p&gt;Firstly, we have to ensure that the DOM has fully loaded before trying to select our elements, so we wrap the function like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// our code goes here&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event"&gt;More about this here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, let's define our &lt;code&gt;createCarousel&lt;/code&gt; function, and just return early in case of some easy to detect errors, for example, if there isn't a window object, or the selector (the HTML element we want to create a carousel with) hasn't been passed to the function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createCarousel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cannot find element to create a carousel into&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's pick up some HTML elements relative to the selector that we're going to need shortly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;carouselItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-carousel-item]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;carouselItemsArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;carouselItems&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;indicatorTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#carousel-indicator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// define interval timer so we can clear it later&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;intervalInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this bit of code we're getting the slides themselves. But using &lt;code&gt;querySelector()&lt;/code&gt; returns a &lt;code&gt;NodeList&lt;/code&gt; collection instead of an &lt;code&gt;Array&lt;/code&gt;, so I'm making it into an &lt;code&gt;Array&lt;/code&gt; that'll be handy later on. We're also getting the indicator template, and also setting an &lt;code&gt;interval&lt;/code&gt;, which is a timer we use to know how long we should wait before scrolling to the next slide. I want to define it here but set it as &lt;code&gt;null&lt;/code&gt; so we can assign it and reference it in other functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Helper functions
&lt;/h2&gt;

&lt;p&gt;Now for some helper functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getActiveItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-carousel-item="active"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPositionOfItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;carouselItemsArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findIndex&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;carouselItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;carouselItem&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setItemAsActive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-carousel-item&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tw-hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tw-block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// update the indicators if available&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentItemIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getPositionOfItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;indicators&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-carousel-indicator]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;indicators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;indicators&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;currentItemIndex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aria-current&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tw-text-primary-600&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tw-text-white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tw-text-white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aria-current&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;false&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tw-text-primary-600&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;indicator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tw-text-white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setItemAsInactive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-carousel-item&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tw-hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tw-block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. &lt;code&gt;getActiveItem()&lt;/code&gt; (no parameters)
&lt;/h3&gt;

&lt;p&gt;At several points, we're going to need to get the currently active item. And since that changes frequently, I don't want to store it in a variable in the main function's scope since that will mean it will only be called once and hold the same value. Instead I have made a small function for this so I could more easily determine what the intent of this selector is.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;getPositionOfItem(item)&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;As it says, this one finds the position of a slide as an index of the array we created earlier.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;setItemAsActive(item)&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This one is a bit more complicated but it's name should inform you of what it does. As well as making an item active, it also updates the indicators (if they exist) to identify the index of the slide.&lt;/p&gt;

&lt;p&gt;This was tricky because you have to match the index of a slide with the correct index of the indicators, and you have to remove the active classes from other indicators too.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;code&gt;setItemAsInactive(item)&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;I could possibly have done this in the function above, but that would combine two intents into one function, which would make it harder to understand. It would also mean adding an extra parameter which complicates things somewhat. This way, both functions that set items active and inactive and, if something else needs to happen when slides change, they're easily identifiable and manageable.&lt;/p&gt;

&lt;p&gt;That's it for the helpers, now I want to set up some actions that the carousel will perform:&lt;/p&gt;

&lt;h2&gt;
  
  
  Actions
&lt;/h2&gt;

&lt;p&gt;Actions define either responses to user interactions or things that happen at a set interval.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;cycle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;intervalInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intervalInstance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;slideTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getActiveItem&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setItemAsInactive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setItemAsActive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;cycle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;nextItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getActiveItem&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeItemPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getPositionOfItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeItemPosition&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;carouselItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// if it is the last item, set first item as next&lt;/span&gt;
        &lt;span class="nx"&gt;nextItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;carouselItems&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;nextItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;carouselItems&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;activeItemPosition&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;slideTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;prevItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getActiveItem&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeItemPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getPositionOfItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeItemPosition&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;prevItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;carouselItems&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;carouselItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;prevItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;carouselItems&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;activeItemPosition&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;slideTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prevItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. &lt;code&gt;cycle()&lt;/code&gt; (no parameters)
&lt;/h3&gt;

&lt;p&gt;Cycle sets up the interval that we defined at the start of the &lt;code&gt;createCarousel&lt;/code&gt; function to 3 seconds and then shows the next carousel item.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;pause()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Pause clears the interval so when the next slide shows we start again from 3 seconds instead of whatever was remaining in the timer. I named it &lt;code&gt;pause&lt;/code&gt; because I thought I might allow external access to the function so that the slider can be paused. In retrospect, it doesn't do that yet so I should have changed the name to reflect what it does.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;slideTo(nextItem)&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Because this function takes the next item we want to set as active as a parameter, we can call this function any time the user clicks on a prev or next button, or the indicators.  It uses the two helper functions to change the active item, resets the interval and then cycles the carousel.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;code&gt;next()&lt;/code&gt; (no parameters)
&lt;/h3&gt;

&lt;p&gt;There's a little trick to this one which means we have to find out if we're on the last item, and if so, the next slide to show should be the first item. See how converting the &lt;code&gt;NodeList&lt;/code&gt; into an &lt;code&gt;Array&lt;/code&gt; came in handy?&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;code&gt;prev()&lt;/code&gt; (no parameters)
&lt;/h3&gt;

&lt;p&gt;This is functionally very similar to the &lt;code&gt;next()&lt;/code&gt; function except it's only called by someone hitting the "previous" button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialize carousel
&lt;/h2&gt;

&lt;p&gt;I could have put this segment of code into the main closure of the &lt;code&gt;createCarousel()&lt;/code&gt; function, but I think having it as a separate body makes it easier to see the intent of what we're doing and it's also clearer to read.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getActiveItem&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;carouselItems&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tw-absolute&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tw-inset-0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * if no active item is set then first position is default
    */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;slideTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;slideTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * Add event listeners to the buttons if they exist
     */&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-carousel-next]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;nextButton&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;nextButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prevButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-carousel-prev]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;prevButton&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;prevButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we're leveraging the small helper and action functions we defined above to set some classes on each of the items, making sure the user sees the active item. This also covers the case that the user assigns a slide other than the first slide as the one that should initially be active. It then adds event listeners to the next and previous buttons if they exist in the DOM.&lt;/p&gt;

&lt;p&gt;Lastly in the main body of the &lt;code&gt;createCarousel()&lt;/code&gt; function we call these functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// if we have an indicator template, create the indicators&lt;/span&gt;
  &lt;span class="nx"&gt;indicatorTemplate&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;createIndicators&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like I said, this last step could've been avoided if I'd put the body of &lt;code&gt;init()&lt;/code&gt; and &lt;code&gt;createIndicators()&lt;/code&gt; functions in the main function, I merely thought this was better from an organizational point of view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create all of the Carousels
&lt;/h2&gt;

&lt;p&gt;Here's the final step, which we'll do outside of our &lt;code&gt;createCarousel()&lt;/code&gt; function but still inside the &lt;code&gt;DOMContentLoaded&lt;/code&gt; listener:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allCarousels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-carousel]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;allCarousels&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;carouselElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;createCarousel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;carouselElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which is to find all of the DOM elements with data-carousel attributes and create a carousel for them. Now each carousel will have it's own indicators, event listeners and intervals which run independently of each other.&lt;/p&gt;

&lt;p&gt;And also, if there are no elements found, the &lt;code&gt;createCarousel&lt;/code&gt; function won't be called at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Improvements
&lt;/h2&gt;

&lt;p&gt;Now that the basic carousel functionality is complete, what could I do to further improve the code?&lt;/p&gt;

&lt;p&gt;I've seen a lot that I can do.&lt;/p&gt;

&lt;p&gt;For example, there's no API. I would like to allow access to some things such as the interval timer so that the speed with which carousel items cycle can be defined on a per-use basis. I'd also like to allow access to other things such as the prev and next buttons. Perhaps analytics need to be added to these listeners in some cases.  Or perhaps someone will need to pause the carousel programmatically for some reason.&lt;/p&gt;

&lt;p&gt;There's also no transitional animation, the slides swap quickly without any visual nicety. It would be good to be able to offer a range of animations into the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Creating this carousel wasn't just a technical challenge. It was also a communication challenge: how do I build this in a way that is easy to understand for those needing to modify my code? Does it explain itself easily? Does it allow another engineer to focus on their task and not ask too many mental gymnastics of them?&lt;/p&gt;

&lt;p&gt;This is what we must aim for when we're writing modules like this; we must be first and foremost kind to both other engineers who will come along later, and our future selves.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/endymion1818/8119f7af21db1f62d9119581fc3a8d19"&gt;Full code here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tailwindcss</category>
      <category>ui</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Introducing Webiny Enterprise Headless CMS+</title>
      <dc:creator>Ben Read</dc:creator>
      <pubDate>Tue, 18 Oct 2022 12:41:25 +0000</pubDate>
      <link>https://forem.com/webiny/introducing-webiny-enterprise-headless-cms-2dlb</link>
      <guid>https://forem.com/webiny/introducing-webiny-enterprise-headless-cms-2dlb</guid>
      <description>&lt;p&gt;We have struggled for a long time on the question of how to position Webiny as a product. From the start, it was clear that we we were building was a feature-rich product that didn't quite sit firmly in the camp of "Headless CMS", yet that is what the majority of our users focused in on. &lt;/p&gt;

&lt;p&gt;As a result, we have doubled down on that particular marketing, building more features and explanation around the concept of Webiny as a Headless CMS. However, as I'm sure you realize, it's an extremely crowded space. And we still didn't quite fit in.&lt;/p&gt;

&lt;p&gt;You see, Webiny isn't just one product. It's four fundamental ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content management system&lt;/li&gt;
&lt;li&gt;File storage and Management&lt;/li&gt;
&lt;li&gt;No-code Web Page Builder&lt;/li&gt;
&lt;li&gt;Form builder&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In reality, it's actually &lt;em&gt;more&lt;/em&gt; than even those products: Not every CMS can scale to tens of thousands of users, serve content for millions of website pages and application interfaces, and be as easily customizable as Webiny is. &lt;/p&gt;

&lt;p&gt;We frequently hear about teams using Webiny in unique and different ways: from wealth management portals, to BitCoin exchanges, to online course providers, to customer service desks, to a no-code form builder. It's been amazing to watch different teams do all of this with Webiny and more.&lt;/p&gt;

&lt;p&gt;Not every CMS can become all — or any — of these things.&lt;/p&gt;

&lt;p&gt;But if we were to look at the terminology around the kind of tools on the market that have those features ... well, that's not what we want to be either.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if you Need More Than a Headless CMS?
&lt;/h2&gt;

&lt;p&gt;Products that we could compete with that are more than a CMS are varied, but the basic premise is the same: allow large, complex, multinational organizations with content creators in different locations and languages to populate their websites with products, blog posts and other information on a regular basis. This might not just involve writing content, but also creating short-lived product launch pages, gaining feedback using a web-based form, or sharing product literature with vendors and potential contractors.&lt;/p&gt;

&lt;p&gt;Popular acronyms in this space are WCM ("Web Content Manager"), and DXP ("Digital Experience Platforms").&lt;/p&gt;

&lt;p&gt;These systems are often very clunky, with difficult-to-navigate interfaces. Customization options are very limited, and they often require hiring specialized individuals to manage them. Not to mention the fact that they're nearly always hugely expensive SAAS platforms that exist outside the organization's perimeter.&lt;/p&gt;

&lt;p&gt;With the features we have, it would be possible to become a competitor in this space. But Webiny is much more nimble, adaptable and on top of that, is also self-hosted.&lt;/p&gt;

&lt;h2&gt;
  
  
  By Any Other Name
&lt;/h2&gt;

&lt;p&gt;So we stopped and asked ourselves: do tools to manage content on the web really need more acronyms? CMS is a pretty good fit, and even the "headless" part seems to be on it's way to being generally accepted.&lt;/p&gt;

&lt;p&gt;And, we thought, if we adopt that terminology, not only would we be alienating the fact that medium and small businesses can also depend on Webiny for their content, other teams might start to get the impression that we are also a clunky system that's prohibitively expensive, not customizable, and overall difficult to use.&lt;/p&gt;

&lt;p&gt;No, we don't want to pigeonhole ourselves like that. Yet, Webiny &lt;em&gt;is more&lt;/em&gt; than a CMS.&lt;/p&gt;

&lt;h2&gt;
  
  
  An All-in-one Solution
&lt;/h2&gt;

&lt;p&gt;So we're sticking with "Headless CMS" as a description of Webiny. But opening a conversation on that front can sometimes present a problem: what if the organization approaching us doesn't have the requirement of a content store. Perhaps instead theyre looking for a way to dynamically populating content from the CMS into a &lt;a href="https://www.webiny.com/docs/overview/applications/page-builder"&gt;no-code page builder&lt;/a&gt;? Or Perhaps they need an &lt;a href="https://www.webiny.com/docs/overview/applications/apw"&gt;advanced publishing workflow&lt;/a&gt; for their teams to be able to create, edit, update, approve, and publish their content?&lt;/p&gt;

&lt;p&gt;Starting off the conversation "We're a Headless CMS" unfortunately leaves out all of the other cool features we've built, or are planning to build in the next few months.&lt;/p&gt;

&lt;p&gt;These new features will enable organizations to build dynamic content quickly, ahead of the competition, without the involvement of technical teams past the initial setup, and do so at a fraction of the cost of other providers. And to do it at the scale that enterprise organizations require.&lt;/p&gt;

&lt;p&gt;Yes, for those organizations who need that kind of power, Webiny is an all-in-one solution that might be able to replace several costly products that are strung together with digital duct tape, not designed to work together, and are also individually difficult to work with.&lt;/p&gt;

&lt;p&gt;For those people, Webiny isn't just a CMS. It's a CMS &lt;em&gt;Plus&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Webiny Enterprise Headless CMS+?
&lt;/h2&gt;

&lt;p&gt;Plus.&lt;/p&gt;

&lt;p&gt;It's succinct, minimalistic, but covers &lt;em&gt;a lot&lt;/em&gt; of stuff. Webiny is still a Headless CMS. But it's a Headless CMS &lt;em&gt;Plus&lt;/em&gt; all of the other features we've talked about.&lt;/p&gt;

&lt;p&gt;Adding the word "Plus" to the business and enterprise products opens the discussion around Webiny's other capabilities. It allows us to showcase other features whilst not detracting from the core use that many of our users see value in.&lt;/p&gt;

&lt;p&gt;It allows us to start more conversations around the functionality of the Page Builder and Form Builder tools, tools that are going to get some pretty interesting added features in coming months.&lt;/p&gt;

&lt;p&gt;We're really hoping that this new initiative will allows us to open more discussions with organizations looking for, not just a Headless CMS, but something that can solve a lot of the issues around producing content in their business.&lt;/p&gt;

&lt;p&gt;If this is you, please reach out to us! We'd love to hear about the difficulties you're facing. For our Enterprise customers we provide SLA support and consultancy services tailored to help your teams to build a solution that'll be designed around your workflow and specific needs. And we can build custom SSO implementations with your IDP of choice.&lt;/p&gt;

&lt;p&gt;If you're an agency or a smaller business, Webiny is a great fit for you! Use it as a Headless CMS with multi-tenancy built-in, and pay a reasonable cost for each user seat. And add-on optional benefits such as our advanced publishing workflow and headless pages.&lt;/p&gt;

&lt;p&gt;Remember, Webiny is an open-source, self-hosted solution: you can use it at no cost under our MIT license today. We hope this will help you to build smaller projects and thoroughly investigate its capabilities before you make a decision.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://webiny.typeform.com/to/VYffkZlR"&gt;Try a live demo today&lt;/a&gt; to get more background on the product.&lt;/p&gt;

</description>
      <category>react</category>
      <category>serverless</category>
    </item>
    <item>
      <title>🤔 Should I Use Gatsby or Next.js For My Next Project?</title>
      <dc:creator>Ben Read</dc:creator>
      <pubDate>Wed, 05 Oct 2022 10:18:58 +0000</pubDate>
      <link>https://forem.com/webiny/should-i-use-gatsby-or-nextjs-for-my-next-project-4el4</link>
      <guid>https://forem.com/webiny/should-i-use-gatsby-or-nextjs-for-my-next-project-4el4</guid>
      <description>&lt;p&gt;Developers tend to get very passionate about their tools. And rightly so: without the best tools for the job at hand, we couldn't achieve our objectives, make something awesome, or build a better world.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt; is still a major player in terms of frontend development. There's currently a competition of sorts going on between the two React heavyweight frameworks &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; and &lt;a href="https://www.gatsbyjs.com/" rel="noopener noreferrer"&gt;Gatsby&lt;/a&gt;. Which one is better? Which should you learn? Is Gatsby better, or Next.js, for building your next project?&lt;/p&gt;

&lt;p&gt;At Webiny, we're interested in providing developers a backend Headless CMS to compliment your frontend, so it's of deep interest to us which tools are popular. Knowing which ones you're using ... even which ones you are &lt;em&gt;preferring&lt;/em&gt;, influences our product decisions. We think about it when we talk about marketing. And we consider it when we're planning what we build into our open source CMS.&lt;/p&gt;

&lt;p&gt;So we decided to do a bit of critical thinking about whether Next.js or Gatsby is better. We came up with these criteria to match them against. Even so, we're going to leave it up to you as to which one you're going to use!&lt;/p&gt;

&lt;h2&gt;
  
  
  Server Generation and Configuration
&lt;/h2&gt;

&lt;p&gt;Generating React code, or any JavaScript, when an application is built, is a critical metric when it comes to frontend frameworks like Gatsby and Next.js. But we don't think it's &lt;em&gt;speed&lt;/em&gt; that's critical. It's certainly important, but it's one of those metrics that the developers of these frameworks are acutely aware of, so they're going to make sure they keep making advancements in this area.&lt;/p&gt;

&lt;p&gt;What's more interesting is what APIs you have access to on server generation. Does the tool make it easier or more difficult to do what you need to? That's what we're assessing here.&lt;/p&gt;

&lt;p&gt;Whilst Next.js allows access to customize headers, page initialization, add rewrites and redirects with it's &lt;a href="https://nextjs.org/docs/api-reference/next.config.js/introduction" rel="noopener noreferrer"&gt;config&lt;/a&gt; file and custom &lt;a href="https://nextjs.org/docs/advanced-features/custom-app" rel="noopener noreferrer"&gt;&lt;code&gt;_App.js&lt;/code&gt;&lt;/a&gt; component, Gatsby has a &lt;a href="https://www.gatsbyjs.com/docs/reference/config-files/gatsby-config/" rel="noopener noreferrer"&gt;config&lt;/a&gt; file, &lt;a href="https://www.gatsbyjs.com/docs/custom-html/" rel="noopener noreferrer"&gt;a custom &lt;code&gt;html.js&lt;/code&gt; document&lt;/a&gt; (which is similar to Next.js). It also has files to directly access the &lt;a href="https://www.gatsbyjs.com/docs/recipes/pages-layouts/#project-structure" rel="noopener noreferrer"&gt;server-rendered application&lt;/a&gt; and the &lt;a href="https://www.gatsbyjs.com/docs/reference/config-files/gatsby-browser/" rel="noopener noreferrer"&gt;hydrated application in the browser&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Gatsby certainly has a wider variety of configuration open to developers here. But that can also be daunting or unnecessarily complex for someone who just wants to build with React but also have static generation.&lt;/p&gt;

&lt;p&gt;Also, sometimes it's not clear what the files are for. For example, the &lt;a href="https://www.gatsbyjs.com/docs/reference/config-files/gatsby-node/" rel="noopener noreferrer"&gt;gatsby-node.js&lt;/a&gt; file isn't specifically for APIs that run in the server environment (which would be using Node.js), instead, this is where the internal node transformation happens, allowing you to create dynamic pages and custom GraphQL resolvers to your application.&lt;/p&gt;

&lt;p&gt;Admittedly, once you know that, it's great to have access to all of these APIs to customize your data. But we'll get to that a bit more later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation
&lt;/h2&gt;

&lt;p&gt;Whether Gatsby or Next.js documentation is good or bad seems highly subjective. But sometimes documentation can inadvertently hide things developers need to know under generalizations or by accidentally missing things out. Sometimes an API badly explained is worse than not having it documented at all.&lt;/p&gt;

&lt;p&gt;We all know this, which is why we looked at documentation as a marker on whether you should choose Next.js or Gatsby to build your next project.&lt;/p&gt;

&lt;p&gt;Whilst Next.js documentation has a cleaner, focused look, Gatsby's is busier with a 3-column layout. Though sometimes that's a good thing. Developers probably won't get as lost contextually in the Gatsby docs as they might in Next.js.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2Fd5f4029f2c3a1103288df84acfe3da11%2F5a190%2Fgatsby-docs-site.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2Fd5f4029f2c3a1103288df84acfe3da11%2F5a190%2Fgatsby-docs-site.png" alt="Gatsby's documentation site"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We generally prefer the darker code blocks and higher contrast on the Next.js website. But it could be because we're just getting old and our eyes aren't as good as they used to be. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2F94674cc768d9845b010c2ab127ca87a8%2F5a190%2Fnextjs-docs-site.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.webiny.com%2Fstatic%2F94674cc768d9845b010c2ab127ca87a8%2F5a190%2Fnextjs-docs-site.png" alt="The Next.js documentation site"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next.js' strengths come out clearly in it's focused approach towards lower-level APIs such as the &lt;code&gt;getStaticProps()&lt;/code&gt; and &lt;code&gt;getServerSideProps()&lt;/code&gt; functions, and these are explained very well. But there are also some things that are not mentioned except succinctly in an &lt;a href="https://nextjs.org/docs/advanced-features/static-html-export#unsupported-features" rel="noopener noreferrer"&gt;"unsupported features" section&lt;/a&gt;. This can be frustrating when you find out you've built your application to use a certain feature, only to realize it's not supported in the environment you intended to deploy to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Routing
&lt;/h2&gt;

&lt;p&gt;Both frameworks take advantage of file-based routing. &lt;a href="https://nextjs.org/docs/routing/dynamic-routes" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; and &lt;a href="https://www.gatsbyjs.com/docs/how-to/routing/creating-routes/#dynamic-and-authenticated-routing" rel="noopener noreferrer"&gt;Gatsby&lt;/a&gt; have the ability to define dynamic routes as  well, but Gatsby doesn't clearly explain how this works, perhaps assuming you're only looking for this feature if you've already used Next.js.&lt;/p&gt;

&lt;p&gt;What is great about Next.js is the ability to create &lt;a href="https://nextjs.org/docs/api-routes/introduction" rel="noopener noreferrer"&gt;API Routes&lt;/a&gt;. This could be really useful in a lot of situations, especially if you have a sprawling REST API endpoint and want a minimal set of data returned from it, you could massage that data down to only the content you need on your frontend. In fact, Next.js has it's &lt;a href="https://github.com/vercel/micro" rel="noopener noreferrer"&gt;own express-like server-side framework called Micro&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This turns Next.js into a truly isomorphic application, which is meant to have components server side and on the client, working in tandem to produce a single application.&lt;/p&gt;

&lt;p&gt;On the other hand, Gatsby seems to keep these concerns clearly separated, even with their own &lt;a href="https://www.gatsbyjs.com/products/cloud/functions" rel="noopener noreferrer"&gt;Gatsby Functions&lt;/a&gt;, the emphasis is on a separate backend that communicates with the hydrated frontend application.&lt;/p&gt;

&lt;p&gt;Do you like to mix these concerns? Or keep them separate? It depends slightly on what kind of developer you are, and what kind of project you're building, as to which you'll prefer from this point of view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Gatsby and Next.js are two very different tools with unique value propositions: either low-level tools to provide fundamental APIs and free developers from unnecessary tooling, or an ecosystem full of plugins of varying quality, together with a data layer to stitch your content together.&lt;/p&gt;

&lt;p&gt;As we said at the start, this isn't a full roundup of all of their features. Maybe what you're building needs something more specific like &lt;a href="https://astro.build/" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;, &lt;a href="https://nuxtjs.org/" rel="noopener noreferrer"&gt;Nuxt&lt;/a&gt; or &lt;a href="https://kit.svelte.dev/" rel="noopener noreferrer"&gt;SvelteKit&lt;/a&gt;. We only know what we've encountered when building our starters for Next.js and Gatsby.&lt;/p&gt;

&lt;p&gt;Oh we didn't tell you? We've built 1-click deploy starters for Webiny headless CMS.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/webiny/nextjs-starter-webiny" rel="noopener noreferrer"&gt;1-click starter for Next.js and Webiny Headless CMS on Vercel&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://www.gatsbyjs.com/dashboard/deploynow?url=https://github.com/webiny/gatsby-starter-webiny" rel="noopener noreferrer"&gt;1-click starter for Gatsby and Webiny Headless CMS on Gatsby Cloud&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure you check those out. Maybe it'll help you make your mind up ... should you use Gatsby or Next.js? The choice is ultimately yours.&lt;/p&gt;

</description>
      <category>react</category>
      <category>gatsby</category>
      <category>nextjs</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Alternatives to Heroku’s Free Tier</title>
      <dc:creator>Ben Read</dc:creator>
      <pubDate>Sat, 27 Aug 2022 20:23:48 +0000</pubDate>
      <link>https://forem.com/webiny/alternatives-to-herokus-free-tier-hlp</link>
      <guid>https://forem.com/webiny/alternatives-to-herokus-free-tier-hlp</guid>
      <description>&lt;p&gt;Heroku &lt;a href="https://blog.heroku.com/next-chapter"&gt;announced yesterday&lt;/a&gt; that it's extremely popular free tier plans are being phased out starting on November 28 this year (2022). It's the end of an era for a lot of people, particularly independent bloggers who have hobby sites, developers evaluating software for use at their organizations, and non-profits who host their Headless CMS backend. But is there an alternative to Heroku?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Heroku Story
&lt;/h2&gt;

&lt;p&gt;Heroku started as a platform offering hosting to exclusively Ruby-based websites. It's since expanded that option and now a host of "build packs" for other languages exist which can deploy infrastructure to Heroku in one click. This was likely the inspiration for similar "one click deploy" buttons that have been adopted by Netlify, Vercel and others.&lt;/p&gt;

&lt;p&gt;The unique thing about the way Heroku was built is that it leveraged &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Hibernate.html"&gt;AWS' ability to hibernate EC2 instances&lt;/a&gt;. So when you deploy your infrastructure to one of the free-tier "dynos" (a dyno is an environment in which your application can run, similar to containers), after a while of inactivity the dyno would hibernate: it would turn off, or go to sleep. The effect of this was that you couldn't contact the application straight away in that state. It would take around 30 seconds for it to activate, or wake up. This was enough time for Heroku to spin up your dyno again so that it can start accepting requests.&lt;/p&gt;

&lt;p&gt;This method did come with some complications like ephermeral file storage, meaning you couldn't store things like SQLLite databases or upload images alongside your application. But there were other Heroku services and 3rd party integrations to allow you to use databases and store images and other files.&lt;/p&gt;

&lt;p&gt;This was a great help to many people who's main concern was with small blogs built on an open source blogging platform such as Ghost or Strapi. With Heroku you could deploy your application and build a separate, static frontend (which would be hosted on a separate service, such as &lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt; or &lt;a href="https://vercel.com/"&gt;Vercel&lt;/a&gt;). That way, you could write an article, rebuild your static site, and afterwards allow the application to hibernate. And it would be free to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives to Heroku
&lt;/h2&gt;

&lt;p&gt;Looking for an alternative to Heroku depends on what you were using for. Many people had used it to host backend services for small hobby apps that didn't make any money, or for proof-of-concept ideas so they could evaluate tools for their organizations to later use in production.&lt;/p&gt;

&lt;p&gt;If this is what you're looking for, there are a plethora of services which deploy containerized applications to a managed environment and that offer free tiers with different pricing models depending on what databases persistent storage and other services you require.&lt;/p&gt;

&lt;p&gt;Depending on what you were using, it's feasible that you could move your application stack there without too many code changes. In fact, both Fly and Render offer guides for migrating from Heroku:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fly.io/docs/app-guides/speed-up-a-heroku-app/"&gt;Speed up a Heroku App with Fly.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://render.com/docs/migrate-from-heroku"&gt;Migrate from Heroku to Render&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But some other popular alternatives include &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.linode.com/products/managed/"&gt;Linode Managed Hosting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/solutions/managed-cloud-hosting"&gt;Digital Ocean Managed Cloud Hosting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://firebase.google.com/products-build"&gt;Firebase Build&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And there are also the following newer alternatives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://adaptable.io"&gt;Adaptable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qoddi.com/"&gt;Qoddi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could also get quite far by using the serverless functions that now come with (formerly) static hosting providers like &lt;a href="https://vercel.com/docs/concepts/functions/serverless-functions"&gt;Vercel&lt;/a&gt;, &lt;a href="https://www.netlify.com/products/functions/"&gt;Netlify&lt;/a&gt;, and &lt;a href="https://www.gatsbyjs.com/products/cloud/hosting"&gt;Gatsby Cloud&lt;/a&gt;. There are even complex frameworks like &lt;a href="https://redwoodjs.com/"&gt;RedwoodJs&lt;/a&gt; that have been built on top of these providers.&lt;/p&gt;

&lt;p&gt;If you were using Heroku for a Headless CMS for a static frontend, most of the open-source Headless CMS platforms offer their own cloud-based infrastructure which will avoid having to re-platform to a different CMS altogether.&lt;/p&gt;

&lt;p&gt;There is however potentially another problem looming...&lt;/p&gt;

&lt;h2&gt;
  
  
  Stay in Control of your Application Stack
&lt;/h2&gt;

&lt;p&gt;If Heroku can suddenly turn around and kill it's free-for-starters offering, who's to say it won't happen for these other services? Even Vercel and Netlify leverage the raw computing power of cloud providers such as AWS, Azure and Google Cloud, repackaging those services and selling them at a profitable margin.&lt;/p&gt;

&lt;p&gt;For that reason, we think the best alternative is to embrace these foundational computing platforms, instead of standing again on the unsteady ground of a reseller.&lt;/p&gt;

&lt;p&gt;AWS and other providers have a &lt;a href="https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&amp;amp;all-free-tier.sort-order=asc&amp;amp;awsf.Free%20Tier%20Types=*all&amp;amp;awsf.Free%20Tier%20Categories=*all"&gt;free tier&lt;/a&gt; which applies to most of its services. These are specific allowances for each of their services that you can use before you start incurring fees.&lt;/p&gt;

&lt;p&gt;With this and similar offerings from other cloud providers like Google Cloud Platform and Microsoft Azure, you could set up an EC2 instance (similar to a virtual machine), configure it to hibernate in a similar fashion to what Heroku was doing, and move your application to it manually.&lt;/p&gt;

&lt;p&gt;This would take a bit of work, either &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AccessingInstancesLinux.html"&gt;to SSH in and upload your application&lt;/a&gt;, or &lt;a href="https://aws.amazon.com/getting-started/hands-on/set-up-ci-cd-pipeline/"&gt;set up a deployment pipeline&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But there's also another opportunity here that we would like to highlight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embrace Serverless
&lt;/h2&gt;

&lt;p&gt;If you notice, the limits on EC2 instances for containerized applications are time-based. You get a certain number of free hours in traditional "always-on" computing models. After that you need to pay for them. Even though this is quite a generous offering, you could still leave yourself with a substantial bill if you're not careful.&lt;/p&gt;

&lt;p&gt;However, other services are based on the number of invocations. Why not take advantage of these? &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Functions as a service&lt;/strong&gt; with AWS Lambda: 1 million free requests per month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File storage&lt;/strong&gt; You get 5GB of S3 static file storage (think images and static website hosting)&lt;/li&gt;
&lt;li&gt;AWS' no-sql &lt;strong&gt;Database&lt;/strong&gt;, DynamoDB, comes at 25GB of storage for free&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The next time you need to build a proof of concept, or hobby app, you could build your solution using a combination of these and host it indefinitely. It wouldn't need special configuration to hibernate since that is it's natural state.&lt;/p&gt;

&lt;p&gt;Why not try serverless in your next proof of concept or hobby project?&lt;/p&gt;

&lt;p&gt;To do so, you could leverage a framework which allows you to write infrastructure-as-code, such as &lt;a href="https://aws.amazon.com/amplify/"&gt;AWS Amplify&lt;/a&gt;, &lt;a href="https://sst.dev/"&gt;Serverless Stack&lt;/a&gt;, the &lt;a href="https://www.serverless.com/"&gt;Serverless framework&lt;/a&gt;, or &lt;a href="https://www.pulumi.com/"&gt;Pulumi&lt;/a&gt;. There is a bit of an overhead to learning these tools so you will need to research which one is best for your needs.&lt;/p&gt;

&lt;p&gt;But there's an alternative...&lt;/p&gt;

&lt;h2&gt;
  
  
  Webiny: Leverage the Power of Serverless
&lt;/h2&gt;

&lt;p&gt;For a little over two years, we have been building Webiny. It's a Headless CMS you can use for your personal blog ... but it's not only a Headless CMS: it's a fully-featured application framework that you can use to scaffold unique applications that will have access to the existing setup we have built via plugins. &lt;/p&gt;

&lt;p&gt;You don't need to reinvent the wheel of how to store files, or how to build a GraphQL API, or how to build an admin interface because we've already done that for you.&lt;/p&gt;

&lt;p&gt;And what is more, you get all of that for free when you choose the DynamoDB-only option in your setup. You will eventually pay for the amount of requests, storage and database usage ... but only if you exceed the free tier on AWS.&lt;/p&gt;

&lt;p&gt;Once your proof-of-concept has satisfied it's requirements, you can destroy your Webiny instance ... or leave it there for future tinkering!&lt;/p&gt;

&lt;p&gt;And if you want a place to write blog articles, &lt;a href="https://www.webiny.com/enterprise-serverless-cms/headless-cms"&gt;Webiny is built to do that out-of-the-box&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/webiny/webiny-js"&gt;Visit our GitHub repo and give us a star today&lt;/a&gt;. And please let us know if you've built something with Webiny, the world's only serverless Headless CMS.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>linux</category>
      <category>containers</category>
    </item>
    <item>
      <title>Which Headless CMS Should I Use?</title>
      <dc:creator>Ben Read</dc:creator>
      <pubDate>Wed, 08 Jun 2022 12:35:11 +0000</pubDate>
      <link>https://forem.com/endymion1818/which-headless-cms-should-i-use-56h1</link>
      <guid>https://forem.com/endymion1818/which-headless-cms-should-i-use-56h1</guid>
      <description>&lt;p&gt;&lt;strong&gt;A little while ago I was working on a POC / tender for a new CMS. As part of that process, I investigated what JavaScript CMSes there were out there. I was particularly keen on self-hosted ones because I personally prefer keeping ownership of my own data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://deliciousreverie.co.uk/posts/javascript-cms-landscape/"&gt;I wrote about my findings on my blog&lt;/a&gt;, however times have changed and there are even more now!&lt;/p&gt;

&lt;p&gt;This video is an update with a few of the newer ones on the market. Hope it helps someone to evaluate and make a decision for their project!&lt;/p&gt;

&lt;p&gt;Here's the video:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://youtube.com/watch?v=QGVGRqjtx-o"&gt;http://youtube.com/watch?v=QGVGRqjtx-o&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>cms</category>
    </item>
    <item>
      <title>Using Webiny Headless CMS With Our New Starter Kits for Gatsby and NextJS</title>
      <dc:creator>Ben Read</dc:creator>
      <pubDate>Fri, 22 Apr 2022 13:59:46 +0000</pubDate>
      <link>https://forem.com/webiny/using-webiny-headless-cms-with-our-new-starter-kits-for-gatsby-and-nextjs-3naj</link>
      <guid>https://forem.com/webiny/using-webiny-headless-cms-with-our-new-starter-kits-for-gatsby-and-nextjs-3naj</guid>
      <description>&lt;p&gt;There's a hidden cost that comes with the majority of self-hosted headless content management systems: the time you must spend initially setting up the project. &lt;/p&gt;

&lt;p&gt;Some require you to install Docker and run its cryptic terminal commands. Others ask you to write code to set up content models. With some you have to install and manage MongoDB or SQL servers on your local machine which may keep running in the background, eating valuable resources even when you're not using your CMS.&lt;/p&gt;

&lt;p&gt;By contrast, Webiny have always tried to make the experience of getting started with our Headless CMS as easy and quick as possible. &lt;/p&gt;

&lt;p&gt;Now it's easier than ever, especially if you're a fan of Gatsby or NextJS, two of the most popular React site builder frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Official Gatsby Starter for Webiny Headless CMS
&lt;/h2&gt;

&lt;p&gt;Gatsby revolutionised the way frontend development was done. It wasn't the first static site generator, but it was the first to bring React and JavaScript as first-class browser tools along with a unified data layer for generating pages at build time from remote data by using GraphQL schema stitching.&lt;/p&gt;

&lt;p&gt;When you partner Gatsby with Webiny, it's an ideal combination! You can &lt;a href="https://www.webiny.com/docs/get-started/install-webiny"&gt;spin up a Webiny instance on your own cloud infrastructure in minutes&lt;/a&gt; (instead of spending hours wrangling with Docker, MongoDB or other code-specific setup), define your own models and write content in the browser, and pair it with your Gatsby site at the click of a single button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GBLpGHOe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.webiny.com/static/8b361314ac8a7d1edaee2d9d0b9f087d/5a190/gatsby.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GBLpGHOe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.webiny.com/static/8b361314ac8a7d1edaee2d9d0b9f087d/5a190/gatsby.png" alt="Gatsby starter rendered from a Webiny backend" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.webiny.com/docs/headless-cms/integrations/gatsby"&gt;Read the Documentation&lt;/a&gt; or &lt;a href="https://github.com/webiny/gatsby-starter-webiny"&gt;go directly to the Gatsby code for a 1-click deploy button to Gatsby Cloud&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Official NextJS Starter for Webiny Headless CMS
&lt;/h2&gt;

&lt;p&gt;NextJS has revolutionised web development because of it's flexibility. It implements the React framework, along with a custom router, server-side data fetching and other tools, but is unopinionated and allows you to form your code however you wish. For that reason, it's been the tool of choice for organisations and individuals worldwide.&lt;/p&gt;

&lt;p&gt;Now it's even easier to get started with Webiny CMS and Next. &lt;a href="https://www.webiny.com/docs/get-started/install-webiny"&gt;Install Webiny on your own cloud infrastructure in minutes&lt;/a&gt;, which will avoid any issues with setting up MongoDB / Docker or some other tool on your machine, or writing code to generate content models.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KvI3xhBz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.webiny.com/static/32f0fbbfa1190b58fab04a22914d1463/5a190/next.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KvI3xhBz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.webiny.com/static/32f0fbbfa1190b58fab04a22914d1463/5a190/next.png" alt="NextJS starter rendered from a Webiny backend" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you're ready, fetch the content on your NextJS site in minutes. &lt;a href="https://www.webiny.com/docs/headless-cms/integrations/nextjs"&gt;Take a look at the documentation&lt;/a&gt; or &lt;a href="https://github.com/webiny/nextjs-starter-webiny"&gt;view the starter for a 1-click deploy button to Vercel&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Starter Should We Build Next?
&lt;/h2&gt;

&lt;p&gt;We don't want to stop there! &lt;/p&gt;

&lt;p&gt;Do you like the view from Vue? Do you like to keep your apps svelte with Svelte? Or perhaps it's time to put Remix into the mix.&lt;/p&gt;

&lt;p&gt;Please tell us by &lt;a href="https://twitter.com/WebinyCMS"&gt;tweeting to us&lt;/a&gt; or via our &lt;a href="https://webiny-community.slack.com/"&gt;Community Slack channel&lt;/a&gt; what starter you would like to see next!&lt;/p&gt;

&lt;p&gt;We hope you enjoy using these tools to get started quickly with Webiny Headless CMS. Please join our community if you get stuck, or to give us feedback you'd like to offer.&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>react</category>
      <category>javascript</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Using react-query with NextJS router</title>
      <dc:creator>Ben Read</dc:creator>
      <pubDate>Wed, 26 Jan 2022 09:08:53 +0000</pubDate>
      <link>https://forem.com/endymion1818/using-react-query-with-nextjs-router-kk8</link>
      <guid>https://forem.com/endymion1818/using-react-query-with-nextjs-router-kk8</guid>
      <description>&lt;p&gt;&lt;strong&gt;Tanner Lindsey's &lt;a href="https://react-query.tanstack.com"&gt;React Query&lt;/a&gt; is a fantastic tool which I've really enjoyed using recently. But we found a few times where we have been using it in close conjunction with multiple requests where it seemed to be returning the same dataset for different queries. Here's what was going on.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here are the two queries I wrote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useReactQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;moreData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useReactQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;getMoreData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, we were fetching data from 2 different endpoints, but when we inspected the data, it seemed to us as if we had made only one API call.&lt;/p&gt;

&lt;p&gt;The problem was that we are using the same &lt;code&gt;key&lt;/code&gt; for each of the queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useReactQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;moreData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useReactQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;moreData&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;getMoreData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using unique keys for each request is in fact &lt;a href="https://react-query.tanstack.com/guides/query-keys"&gt;well documented on the docs site&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Query keys and NextJS routing
&lt;/h2&gt;

&lt;p&gt;I encountered a similar problem when using keys similar to the above on two different pages in a NextJS application. After the original query had been run we did a &lt;code&gt;router.push()&lt;/code&gt; to a new page, and had another &lt;code&gt;useQuery()&lt;/code&gt; on the next page which used the same key as the previous page.&lt;/p&gt;

&lt;p&gt;Again the same thing happened, it seemed to us as if we had made a mistake and mixed the fetch requests around. this happened because changing a route in a hydrated NextJS application only changes the application state, not the browser URL, which would have cleared the react-query state. Ensuring that the keys were unique fixed the issue here as well.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>How to test React hooks ... by not testing react hooks</title>
      <dc:creator>Ben Read</dc:creator>
      <pubDate>Thu, 09 Dec 2021 10:18:09 +0000</pubDate>
      <link>https://forem.com/endymion1818/how-to-test-react-hooks-by-not-testing-react-hooks-37j5</link>
      <guid>https://forem.com/endymion1818/how-to-test-react-hooks-by-not-testing-react-hooks-37j5</guid>
      <description>&lt;p&gt;&lt;strong&gt;React lifecycle methods are a pain to test. How do you know if your component mounted twice? How do you test for that? Why should you, since it's part of React's internal behaviour? Here's how we've started testing React hooks ... by not testing them at all!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Say I have my component written and it updates some messages from my API using a useEffect hook like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messagesToUpdate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getAllMessagesToUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;messagesToUpdate&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="nx"&gt;messagesToUpdate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;edge&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;updateMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;UpdateMessageInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateMessage&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How do I test each permutation of this call? I would need to mock the render cycle so that I can ensure that each time useEffect is called, this message does as it's expected.&lt;/p&gt;

&lt;p&gt;Here's an alternative. Abstract the useEffect call into its own hook, and abstract the functionality into a separate function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// in my main component&lt;/span&gt;
&lt;span class="nf"&gt;useUpdateMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// in my hook&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useUpdateMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;updateMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateMessage&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// in my standalone function&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messagesToUpdate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getAllMessagesToUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;messagesToUpdate&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="nx"&gt;messagesToUpdate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;edge&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;updateMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;UpdateMessageInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I don't need to worry about React lifecycle methods. I can test my hook if I want to, but it hardly seems worth it here. If I would, it would be easier to do so using &lt;a href="https://react-hooks-testing-library.com/"&gt;react-hooks-testing-library&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, I only need to worry about testing my standalone function, and that can be done much more easily now it's separated away from React's internal mechanism.&lt;/p&gt;

&lt;p&gt;I like this approach a lot. Separate your code from the framework you're using. Even though there are more moving parts, it can facilitate much easier testing and a lot cleaner code.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks to my colleague Stuart Nichols for this idea!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
