“Dots” Custom Element (aka Web Component)

Please accept my half-hearted apology for the awkward title. Web Components are hot right now but the name is considered harmful according to Mayank. I think Mayank makes a strong argument, but I also want the brand recognition and “SEO juice”, so I’m being apologetically greedy with the title.
The Prototype
Anyway, I’ve been coding up a […]


This content originally appeared on dbushell.com and was authored by dbushell.com

Please accept my half-hearted apology for the awkward title. Web Components are hot right now but the name is considered harmful according to Mayank. I think Mayank makes a strong argument, but I also want the brand recognition and “SEO juice”, so I’m being apologetically greedy with the title.

The Prototype

Anyway, I’ve been coding up a “Dots” custom element. Dots are those little fiddly buttons that are used to navigation carousels and such. Yeah I know carousels are bad and I do protest but it’s not always my decision.

Look at this GIF:

interacting with the “Dots” component

See my “Dots” prototype on CodePen.

Obviously a bunch of dots are useless on their own. I plan to pair them with a carousel component I will create next. Isn’t this already a solved problem? I’ve been researching many carousels all of which are implemented differently. My goal is to build a set of composable elements that can make a carousel or be used elsewhere. Where else can “Dots” be used? I don’t know, stop asking hard questions.

I’m looking for feedback, especially on accessibility. Reach me on Mastodon.

Stylin’

This is the least important aspect of the component. I’ve added just enough CSS for the demo to look nice. The only thing worth noting is the hidden pseudo-element I’m using to expand the target area. Dots are 50% larger than they look and thus easier to poke. The elongated effect is optional and easily removed. I’m not sure if I like it myself, but it’s trendy, and makes for a more interesting visual demo.

Accessible Markup

I figured this would be an simple custom element to code but it’s surprisingly tricky. What is the correct markup? I could use a list of buttons:

<dots-component>
  <ol>
    <li>
      <button type="button" aria-selected="true">
        <span>Item 1</span>
      </button>
    </li>
    <li>
      <button type="button">
        <span>Item 2</span>
      </button>
    </li>
  </ol>
</dots-component>

Many examples I’ve researched use a list. Most like Splide use <ul> but surely <ol> like Flickity uses is correct? Regardless, I’ve opted to go without:

<dots-component>
  <button type="button" aria-selected="true">
    <span>Item 1</span>
  </button>
  <button type="button">
    <span>Item 2</span>
  </button>
</dots-component>

Screen readers can announce the list numbers which sounds redundant and confusing with the button labels. I’m adding role attributes later which affects this too.

I pondered if <button> was correct or whether I should use <a href="#item-1"> allowing for more progressive enhancement. Since I’m not building the entire carousel (yet) I can’t assume fragment links are implemented correctly.

Bootstrap’s carousel forgoes any interactive element and expects the user to click a three pixel tall <li> — oh dear. Glide carousel is unlisted with <button> elements that have no label. Neither claim to be accessible and Bootstrap even says “carousels are generally not compliant with accessibility standards”. At least they’re honest.

For button labels I considered using aria-label instead of the hidden span. W3C’s example takes this approach. However, W3C also say the first rule of ARIA is that you do not talk, I mean, let’s just quote the doc:

If you can use a native HTML element or attribute with the semantics and behaviour you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.

In short: “If you can use [native HTML] then do so”. So I did so. Chris Ferdinandi has written about other issues using aria-label so I’m avoiding it.

Patterns like Filament Group’s carousel use role="tablist" on the wrapper role="tab" on the buttons. I’ve added these roles as it’s one practice that seems uncontested. Apple’s VoiceOver does announce the number of tabs during navigation.

Using aria-selected on the active button is the accepted attribute for tabs. Initially I thought aria-current but that is more appropriate for pagination. Of course, I could use a HTML class for styling, adhering to ARIA rule 1, but this attribute adds context and styling with [aria-selected="true"] is a bonus.

The final markup looks like this:

<dots-component role="tablist">
  <button type="button" aria-selected="true" tabindex="0" role="tab">
    <span>Item 1</span>
  </button>
  <button type="button" aria-selected="false" tabindex="-1" role="tab">
    <span>Item 2</span>
  </button>
</dots-component>

The custom element JavaScript adds & updates the attributes as needed (notes on tabindex below). What do you think? I’m looking for feedback.

Interactive API

I’m more confident on the interactivity stuff. The JavaScript is fairly robust and can handle DOM modifications if buttons are added, removed, etc. Query selectors are not cached (over optimisation). A single click event listener is registered to handle pointer navigation.

I’ve implemented “The Looper” pattern as Adam Argyle names it. Basically, like radio input groups, only the active element is in the document tab sequence. This is done using tabindex attributes. When a button is focused the whole group can be cycled through using the arrow keys. Right-to-left content flow is accounted for.

Attributes

My component supports a disabled attribute.

<dots-component disabled>
  <!-- buttons -->
</dots-component>

Attributes can be toggled with JavaScript in two ways:

const dots = document.querySelector('dots-component');
dots.disabled = true; // set directly
dots.removeAttribute('disabled'); // use methods

The disabled attribute is passed down to all <button> children syncing their state.

Jeremy Keith suggests using the data- prefix for custom attributes to avoid clashing with possible future global attributes. I recently criticised HTMX for using hx- over data-. Angular does the same with ng-. But those two examples don’t usually use custom elements. Custom elements allow for unprefixed attributes which surprises me. There’s a lot more discussion but it appears the horse has bolted on this one.

I think my disabled attribute is safe — I say hesitantly — because I’m imitating one that already exists. That said, it’s not identical because the :disabled CSS pseudo-class doesn’t work. So perhaps I’m committing the worst sin…

There is a newer ElementInternals API that allows for custom state:

class DotsElement extends HTMLElement {
  #internals;
  connectedCallback() {
    this.#internals = this.attachInternals();
    this.#internals.states.add('disabled');
  }
}

Custom state can be selected with CSS:

dots-component {
  &:state(disabled) {
    pointer-events: none;
  }
}

This state does not directly translate to HTML attributes; they must be connected manually. For my use case I still need to propagate the disabled attribute down. In the full code I’m using get/set methods along with attributeChangedCallback.

The question remains do I stick with disabled or used data-disabled for the <dots-component> custom element?

Events and Methods

My custom element dispatches a change event.

const dots = document.querySelector('dots-component');
dots.addEventListener('change', (ev) => {
  console.log(
    `Index: ${ev.detail.activeIndex}`,
    `Label: "${ev.target.activeElement.innerText}"`
  );
});

The element has a few methods too:

dots.goto(2);
dots.previous();
dots.next();

These should be self explanatory.

Next Step

Play around with the CodePen demo. Send me feedback so I can perfect it. I feel it’s in a good place despite being completely useless by itself!

As mentioned I plan to build the other piece(s) of a carousel pattern. I’ve attempted that before with my Mostly CSS Responsive Carousel. That version doesn’t use custom elements and the “dots” were an afterthought.

By the way, I’m using the name <dots-component> for lack of a better idea. Suggestions are welcome. Have a good day!


This content originally appeared on dbushell.com and was authored by dbushell.com


Print Share Comment Cite Upload Translate Updates
APA

dbushell.com | Sciencx (2024-06-12T10:00:00+00:00) “Dots” Custom Element (aka Web Component). Retrieved from https://www.scien.cx/2024/06/12/dots-custom-element-aka-web-component/

MLA
" » “Dots” Custom Element (aka Web Component)." dbushell.com | Sciencx - Wednesday June 12, 2024, https://www.scien.cx/2024/06/12/dots-custom-element-aka-web-component/
HARVARD
dbushell.com | Sciencx Wednesday June 12, 2024 » “Dots” Custom Element (aka Web Component)., viewed ,<https://www.scien.cx/2024/06/12/dots-custom-element-aka-web-component/>
VANCOUVER
dbushell.com | Sciencx - » “Dots” Custom Element (aka Web Component). [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/06/12/dots-custom-element-aka-web-component/
CHICAGO
" » “Dots” Custom Element (aka Web Component)." dbushell.com | Sciencx - Accessed . https://www.scien.cx/2024/06/12/dots-custom-element-aka-web-component/
IEEE
" » “Dots” Custom Element (aka Web Component)." dbushell.com | Sciencx [Online]. Available: https://www.scien.cx/2024/06/12/dots-custom-element-aka-web-component/. [Accessed: ]
rf:citation
» “Dots” Custom Element (aka Web Component) | dbushell.com | Sciencx | https://www.scien.cx/2024/06/12/dots-custom-element-aka-web-component/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.