Tab Widget with Closeable Tabs

Demo

Reset

Apples

No explicit default was set, so per standard functionality this first tabpanel will be shown by default.

Here's a link just to test keyboard tabbing. (it's odd talking about tabs, and tabs.)

Bananas

This is an example of a heading being used to populate the tab's label, while also not removing heading from the tabpanel. This is done by adding the value "keep" to data-atabs-heading.

There are two headings here

The tab is getting its label from the data-atabs-tab-label. The heading is kept in the tabpanel because it does not have the data-atabs-heading attribute.

It wouldn't make much sense

If the contents of a tabpanel contain additional sub headings, it wouldn't make sense to remove the initial heading from the tabpanel. Doing so would create a gap in the heading structure.

Special demo: enabled a disabled tab

There are some cases, e.g. in a multi-step process, where a user completes a task in an exposed tab panel or panels, and then by completion of that task a previously disabled tab becomes enabled.

The following button showcases how that could be implemented for the disabled "Mangos" tab.

Durian

The durian (/ˈdjʊəriən/)[2] is the edible fruit of several tree species belonging to the genus Durio. There are 30 recognised Durio species, native to Indonesia, Malaysia, the Philippines, and Thailand; at least nine of which produce edible fruit.[3][4] Durio zibethinus, native to Borneo and Sumatra, is the only species available in the international market.[citation needed] It has over 300 named varieties in Thailand and 100 in Malaysia, as of 1987. Other species are sold in their local regions.[3]

Named in some regions as the "king of fruits",[5] the durian is distinctive for its large size, strong odour, and thorn-covered rind. The fruit can grow as large as 30 centimetres (12 inches) long and 15 cm (6 in) in diameter, and it typically weighs 1 to 3 kilograms (2 to 7 pounds). Its shape ranges from oblong to round, the colour of its husk green to brown, and its flesh pale yellow to red, depending on the species.

Mangos (out of stock)

This text will never be visible, unless the tab is enabled via JS. Please refer to the demo button in the "Oranges" tab for an example how this could be achieved.

Lychee

Lychee, (Litchi chinensis), also spelled litchi or lichi, evergreen tree of the soapberry family (Sapindaceae), grown for its edible fruit. Lychee is native to Southeast Asia and has been a favourite fruit of the Cantonese since ancient times. The fruit is usually eaten fresh but can also be canned or dried.

Dragon Fruit

These fruits are commonly known in English as "dragon fruit", a name used since around 1963, apparently resulting from the leather-like skin and prominent scaly spikes on the fruit's exterior.[3] The names pitahaya and pitaya derive from Mexico, and pitaya roja in Central America and northern South America, possibly relating to pitahaya for names of tall cacti species with flowering fruit.[2][4] The fruit may also be known as a strawberry pear.[5]

Mangosteen

Mangosteen (Garcinia mangostana), also known as the purple mangosteen,[1] is a tropical evergreen tree with edible fruit native to tropical lands surrounding the Indian Ocean. Its origin is uncertain due to widespread prehistoric cultivation.[2][3] It grows mainly in Southeast Asia, southwest India and other tropical areas such as Colombia, Puerto Rico and Florida,[2][4][5] where the tree has been introduced. The tree grows from 6 to 25 metres (20 to 82 feet) tall.[2] The fruit of the mangosteen is sweet and tangy, juicy, somewhat fibrous, with fluid-filled vesicles (like the flesh of citrus fruits), with an inedible, deep reddish-purple colored rind (exocarp) when ripe.[2][4] In each fruit, the fragrant edible flesh that surrounds each seed is botanically endocarp, i.e., the inner layer of the ovary.[6][7] The seeds are of similar size and shape to almonds.

Mangosteen belongs to the same genus as the other, less widely known fruit, such as the button mangosteen (G. prainiana) or the charichuelo (G. madruno).

Documentation

Overview

Afford each tab a “close” operation with a universally percievable signifier, enabling all users to delete that tab and its corresponding panel from the set.

Sample use case

A web-based text editor that allows users to open multiple text files simultaneously and collectively presents them in a tabbed interface. Each open file’s tab presents an button which allows the user to close that file.

Requirements

  • Do not sabotage reporting of the tablist, tab, and tabpanel roles to assitive tech.
  • Ensure keyboard operability of the close operation’s perceivable signifier while conforming to the keyboard interactions recommended by WAI-ARIA.
    • WAI-ARIA says: When the tab list contains the focus, [Tab] moves focus to the next element in the page tab sequence outside the tablist, which is the tabpanel unless the first element containing meaningful content inside the tabpanel is focusable. My solution violates the part I’ve bolded, but I wonder if there’s wiggle room on that point.
  • Enable the user to also perform the close operation by pressing the Delete while focused on the desired tab.
  • Set focus predictably upon deletion of tab.
  • Provide a sufficiently descriptive text label for the close button.
  • Do not afford disabled tabs the close operation.

Test coverage

  • Tested with:
    • Mac OS Catalina 12.1 + VoiceOver + Safari 15.3. Promising results, but needs more testing.
    • Windows 10 + NVDA + Chrome. Promising results, but needs more testing.
  • Not yet tested with:
    • iOS
    • Android
    • JAWS
    • High contrast mode

Known Issues

  • New styles are quick & dirty.
  • I temporarily disabled styles that appeared to target high contrast mode while incorporating my own changes. Need to investigate and re-introduce.
  • I broke the vertical orientation styles. (>_<)
  • New JavaScript is quick & dirty, and I may have carelessly introduced ES6 dependency.
  • The close button’s visible X is an SVG currently embedded in the JavaScript. Yuck.
  • Deletion of the last remaining tab fails to set appropriate focus to another logical element onthe page. This should probably be configurable for the adopting context, but maybe the first preceding interactive element on the page?
  • Still need to test for tab configurations with:
    • Vertical tabs
    • Nested tabs
    • Disabled tabs
    • Manual activation
  • I’ve rigged it so that when a user has brought focus to a tab’s close button, they may still use arrow keys to navigate the tab list. This feels true to the spirit of the ARIA pattern???
  • I’m still chewing on how best to allow for the close buttons to get sufficiently descriptive labels. The solution should be friendly to localization. For example, it’ll probably be a bad idea to programatically assemble string fragments. Maybe a content author should be expected to provide text for each closeable tab (e.g., data-tab-delete-label="Close the Apples tab"). For now, I’m just hardcoding an English label of "Close tab" inside the JavaScript.
  • Unrelated to my own changes, but I’m thinking disabled tabs (and the disabled close buttons) they contain should not visibly react to hover as strongly as they currently do, if at all?

Additional feedback

  • Is role="presentation" actually achieving anything for wrapping the tab and the close button?
  • Take care to manage focus correctly for RTL directionality (and others?) on deletion (i.e., ARIA recommend *next* tab gets focus, which would be on left for RTL)
  • What is a good experience for deleting the last tab?
    • Prevent deletion of the last tab altogether?
    • Automatically insert a "blank" tab with feedback?
    • Allow it, but have a sensible default with hook for contextual fallback: Focus works its way back up to the previous heading? Previous interactive element? Next interactive element?
  • Optional user confirmation required for deletion? Some contexts may need this to satisfy WCAG AA 3.3.4. Prototype and get visual design direction later. But still make optional, and on a tab-by-tab basis (e.g., unsaved content in one tab).