@use "/shell" as *; @use "quark:list" as list; #release-notice { /* opens once: sheet re-runs restart a delay, so gate it on the fact it writes */ &:not([data-did-open]) { @delay 7000 { is-open: ""; data-did-open: ""; } } /* prevent clicks from bubbling to the sheet */ @on mouseup, click (stop-propagation); } provider-fetch[api-url*="package-metas/index.json"][is-success] { $package-indices: prop("provision").body; /* `excom.navGroup` moves a package out of its type's list, into that group */ $ungrouped: list.reject($package-indices.packages, "navGroup"); .package-links { [bind-elements] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "kit-element")); } [bind-element-bases] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "element-base")); } [bind-tools] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "tool")); } /* the standalone libraries: same rows, rendered flat (shell.css) */ [bind-libraries] ul:not(ul ul) { content: iterate(getPackagesByType($ungrouped, "library")); } [bind-library-group] ul:not(ul ul) { content: iterate(list.filter($package-indices.packages, "navGroup", "libraries")); } ul:not(ul ul) > li { $pkg: item.shortName; > spa-a { route-href: "/packages/#{item.shortName}"; } } /* one `content` rule per link: a second one would rewrite the first's text on every pass */ ul:not(ul ul, [bind-libraries] ul) > li > spa-a { content: item.shortName; } [bind-libraries] ul:not(ul ul) > li > spa-a { content: displayName(item.shortName); } /* packages whose docs span several pages: one collapsible group per section */ [bind-sections] { content: iterate(item.docSections); summary { content: item.title; } details > ul { content: iterate(item.docs); spa-a { route-href: "/packages/#{$pkg}/#{item.name}"; content: item.title; } } details:not(:has(spa-a[is-active])) { open: none; } } } /* page titles: the page, then the site. The docs home keeps the title in the document head; the other routes carry theirs in markup */ spa-route[is-active] { /* a site guide */ &[route-href$=":name"] { $title-guide: list.find($package-indices.docs, "name", $route.params.name); document-title: "#{$title-guide.title or $route.params.name} · Nucleus · docs"; } /* a package README */ &[route-href$=":packageName"] { document-title: "#{displayName($route.params.packageName)} · Nucleus · docs"; } /* one page of a package's docs */ &[route-href$=":docName"] { $title-package: list.find($package-indices.packages, "shortName", $route.params.packageName); document-title: "#{docTitle($title-package, $route.params.docName)} · #{displayName($route.params.packageName)} · Nucleus · docs"; } } /* the page's markdown file, for the footer link: the docs home and each guide the index lists, and a package's page whose index entry says `markdown` (build-docs-index sets it; the dev server's index has none, so no link there). siteDocHref and the path check say which URL is the page's own: the 404 and a package's doc pages have none */ > spa-manager[active-url] { $page-path: attr("active-url").split("#").at(0).split("?").at(0); $page-name: if($page-path == SITE_HOME: SITE_HOME_DOC; else: $page-path.split("/").at(-1)); $page-guide: list.find($package-indices.docs, "name", $page-name); $page-package: list.find($package-indices.packages, "shortName", $page-name); $page-markdown: if($page-guide and siteDocHref($page-guide.name) == $page-path: "/docs/#{$page-guide.name}.md"; $page-package.markdown and "#{SITE_BASE}/packages/#{$page-package.shortName}" == $page-path: "/#{$page-package.shortName}.md"); footer [bind-page-markdown] { href: $page-markdown; content: ternary($page-markdown, "Markdown version of this page"); } } } spa-route { $route: prop("provision"); /* paramless routes name their guide in markup (the docs home, `/`); /docs/:name gets it from params */ $route-doc-name: attr("data-doc-name"); } /* the desktop aside and the mobile sheet stamp the same nav template */ [data-site-nav] { details:has(spa-a[is-active]) { /* not kosher */ open: ""; } spa-a[is-active] { aria-current: ""; } spa-a:not([is-active]) { aria-current: none; } } /* mobile sheet: Escape / back gesture while open; a tapped link closes it */ #site-menu { &[is-open] dismiss-watcher { is-active: ""; } &:not([is-open]) dismiss-watcher { is-active: none; } @on click (target: "spa-a") { is-open: none; } } #site-menu-gesture { &:has(> #site-menu[is-open]) { progress-offset: 1; } &:not(:has(> #site-menu[is-open])) { progress-offset: 0; } @on gesture-handler-start { #site-menu { is-scrubbing: ""; } } @on gesture-handler-end { #site-menu { is-open: event.detail.snap == 1; is-scrubbing: none; } } } main { @on copy-source (handle: copySource); } #search-dialog[open] { > include-content { is-active: ""; /* for some reason this is necessary on first render */ @on include-content-did-render (handle: focusInput); } input[type="search"] { /* works on all subsequent opens */ autofocus: ""; } } #search-dialog:not([open]) input[type="search"] { autofocus: none; }

content-tabs

Tabs and accordions — single, multi, or toggle selection, paired by name or position.

Features

  • Single / multi / toggle Radio-like tabs, an accordion, or re-click to close
  • Pair by name or position Match headers to bodies with tab-name, or by index when unnamed
  • Isolated nesting Nested <content-tabs> groups never cross-wire
  • Bindable state .provision is { tabType, openTabs, activeTab } — read the active tab from Quark with prop("provision")
  • Included looks .underline and .file-tabs styles ship built in

Installation

This package is available in the NucleusKit. Or it can be used by itself:

CDN Package Manager
<script src="https://unpkg.com/@excom/kit-utils@0.3.0/dist/index.umd.min.js"></script>
<script src="https://unpkg.com/@excom/neutron@0.2.0/dist/index.umd.min.js"></script>
<script src="https://unpkg.com/@excom/content-tabs@0.1.5/dist/index.umd.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@excom/content-tabs@0.1.5/dist/index.css">
npm install @excom/content-tabs
HTML Imports JS / CSS Imports
<!-- import path to `node_modules` will depend on your build setup -->
<script type="module" src="/node_modules/@excom/content-tabs"></script>
<link rel="stylesheet" href="/node_modules/@excom/content-tabs">
import "@excom/content-tabs";
@import "@excom/content-tabs/index.css";
Peer dependencies (0)

Packages a consumer must install alongside this one. Workspace deps are bundled.

Package Version
View Dist Files

All exports:

.
  • types: ./dist/index.d.ts
  • import: ./dist/index.js
  • default: ./dist/index.js
./content-tabs
  • types: ./dist/content-tabs.d.ts
  • import: ./dist/content-tabs.js
  • default: ./dist/content-tabs.js
./content-tabs-body
  • types: ./dist/content-tabs-body.d.ts
  • import: ./dist/content-tabs-body.js
  • default: ./dist/content-tabs-body.js
./content-tabs-body.js
  • types: ./dist/content-tabs-body.d.ts
  • import: ./dist/content-tabs-body.js
  • default: ./dist/content-tabs-body.js
./content-tabs-body.min
  • types: ./dist/content-tabs-body.d.ts
  • import: ./dist/content-tabs-body.min.js
  • default: ./dist/content-tabs-body.min.js
./content-tabs-body.min.js
  • types: ./dist/content-tabs-body.d.ts
  • import: ./dist/content-tabs-body.min.js
  • default: ./dist/content-tabs-body.min.js
./content-tabs-body.umd.min
  • types: ./dist/content-tabs-body.d.ts
  • default: ./dist/content-tabs-body.umd.min.js
./content-tabs-body.umd.min.js
  • types: ./dist/content-tabs-body.d.ts
  • default: ./dist/content-tabs-body.umd.min.js
./content-tabs-header
  • types: ./dist/content-tabs-header.d.ts
  • import: ./dist/content-tabs-header.js
  • default: ./dist/content-tabs-header.js
./content-tabs-header.js
  • types: ./dist/content-tabs-header.d.ts
  • import: ./dist/content-tabs-header.js
  • default: ./dist/content-tabs-header.js
./content-tabs-header.min
  • types: ./dist/content-tabs-header.d.ts
  • import: ./dist/content-tabs-header.min.js
  • default: ./dist/content-tabs-header.min.js
./content-tabs-header.min.js
  • types: ./dist/content-tabs-header.d.ts
  • import: ./dist/content-tabs-header.min.js
  • default: ./dist/content-tabs-header.min.js
./content-tabs-header.umd.min
  • types: ./dist/content-tabs-header.d.ts
  • default: ./dist/content-tabs-header.umd.min.js
./content-tabs-header.umd.min.js
  • types: ./dist/content-tabs-header.d.ts
  • default: ./dist/content-tabs-header.umd.min.js
./content-tabs.js
  • types: ./dist/content-tabs.d.ts
  • import: ./dist/content-tabs.js
  • default: ./dist/content-tabs.js
./content-tabs.min
  • types: ./dist/content-tabs.d.ts
  • import: ./dist/content-tabs.min.js
  • default: ./dist/content-tabs.min.js
./content-tabs.min.js
  • types: ./dist/content-tabs.d.ts
  • import: ./dist/content-tabs.min.js
  • default: ./dist/content-tabs.min.js
./content-tabs.umd.min
  • types: ./dist/content-tabs.d.ts
  • default: ./dist/content-tabs.umd.min.js
./content-tabs.umd.min.js
  • types: ./dist/content-tabs.d.ts
  • default: ./dist/content-tabs.umd.min.js
./index
  • types: ./dist/index.d.ts
  • import: ./dist/index.js
  • default: ./dist/index.js
./index.bundle.min.css
  • default: ./dist/index.bundle.min.css
./index.css
  • default: ./dist/index.css
./index.js
  • types: ./dist/index.d.ts
  • import: ./dist/index.js
  • default: ./dist/index.js
./index.min
  • types: ./dist/index.d.ts
  • import: ./dist/index.min.js
  • default: ./dist/index.min.js
./index.min.css
  • default: ./dist/index.min.css
./index.min.js
  • types: ./dist/index.d.ts
  • import: ./dist/index.min.js
  • default: ./dist/index.min.js
./index.umd.min
  • types: ./dist/index.d.ts
  • default: ./dist/index.umd.min.js
./index.umd.min.js
  • types: ./dist/index.d.ts
  • default: ./dist/index.umd.min.js

Usage

Put <content-tabs-header> and <content-tabs-body> elements inside <content-tabs>. A header pairs with the body sharing its tab-name, or by position when both are unnamed.

<content-tabs class="underline">
  <content-tabs-header is-open>Overview</content-tabs-header>
  <content-tabs-header>Details</content-tabs-header>
  <content-tabs-body is-open><p>Overview copy.</p></content-tabs-body>
  <content-tabs-body><p>Details copy.</p></content-tabs-body>
</content-tabs>

The open state is a fact, not just an event: .provision holds { tabType, openTabs, activeTab }, where a tab is its header's tab-name (or its index when unnamed) and activeTab is the first open one. Quark reads it on the group and binds it anywhere below:

content-tabs {
  $tab: prop("provision").activeTab;
  h2 { content: $tab; }
}

API Reference

content-tabs

Attributes (1) Role — option is configurable, state is managed by the element (read-only), or hybrid which is both. Role Attribute Type Values Prop Default option tab-type
Selection mode. single opens one header at a time (radio-like); multi allows any number open at once (accordion); toggle is like single, but re-clicking the open header closes it. tabType
string "single" | "multi" | "toggle" "single"
Provision (1) This provision property lives on the DOM node. Read / watch it with Quark’s prop("provision"), or listen for the neutron-provision event from app JS. Property Type provision
Open state: { tabType, openTabs, activeTab }. A tab is its header's tab-name, or its index among this group's own headers when unnamed; activeTab is the first open one (null when none). Set after mount and after every header change (batched with the body sync). Not reflected as an attribute. provision
ContentTabsProvision
Events (1)
Dispatches — events that content-tabs fires and their default actions. Dispatch Default Action
A "default action" is subsequent logic executed by the element if e.preventDefault() is not synchronously called on the event.
Listeners — events that content-tabs listens for which will trigger subsequent actions. Listener Type Action content-tabs-header-opened ContentTabsHeaderOpenedEvent
Closes sibling headers when tab-type is single (default) or toggle.
Commands — verbs content-tabs accepts as a command event (HTML Command API): from a <button command commandfor>, an <event-handler command-name>, or a CommandEvent. Command Action
Recognized Elements (2) Child or descendant elements are recognized by content-tabs and relevant to its functionality. Element Relationship Required content-tabs-body
Panels shown / hidden to match their header.
child true
content-tabs-header
Clickable tab headers.
child true
Styles (10)
Classes — optional classes that change the appearance of content-tabs. Class Description .file-tabs File-explorer look — headers render as bordered folder tabs joined to the active body. .underline Paper / underline look — the active header gets a bottom accent border instead of a bordered card.
Variables — public CSS variables for theming content-tabs. Opt-out:
If you wish to opt-out of these styles on a case-to-case basis, use property all: revert-layer. Valence.css ships this as .unstyled and .unstyled-all for the entire tree.
Variable Syntax Default --content-tabs-active-indicator-height <length> 2px --content-tabs-body-padding <length>{1,4} var(--v-spacing, 15px) --content-tabs-border-color <color> var(--v-muted-border-color, currentColor) --content-tabs-border-width <length> var(--v-border-width, 2px) --content-tabs-color-active <color> var(--v-color-primary, inherit) --content-tabs-header-padding <length>{1,4} 10px 15px
Aliases — @custom-selector synonyms so custom elements can share semantic styles. Element aliases match the host tag / .tag-* class; state aliases match attributes (nested under the element alias). Alias Kind Matches :--content-tab--is-open state [is-open], [aria-selected="true"] :--content-tabs element content-tabs, .tag-content-tabs

content-tabs-body

Attributes (2) Role — option is configurable, state is managed by the element (read-only), or hybrid which is both. Role Attribute Type Values Prop Default option tab-name
Pairs this body with the <content-tabs-header> sharing the same tab-name. Unset headers / bodies pair by position instead. tabName
string null
hybrid is-open
Visibility, kept in sync with the paired header's is-open. Set directly only if this body is not paired with a header. isOpen
boolean false
Provision (0) This provision property lives on the DOM node. Read / watch it with Quark’s prop("provision"), or listen for the neutron-provision event from app JS. Property Type
Events (0)
Dispatches — events that content-tabs-body fires and their default actions. Dispatch Default Action
A "default action" is subsequent logic executed by the element if e.preventDefault() is not synchronously called on the event.
Listeners — events that content-tabs-body listens for which will trigger subsequent actions. Listener Type Action
Commands — verbs content-tabs-body accepts as a command event (HTML Command API): from a <button command commandfor>, an <event-handler command-name>, or a CommandEvent. Command Action
Recognized Elements (0) Child or descendant elements are recognized by content-tabs-body and relevant to its functionality. Element Relationship Required
Styles (1)
Classes — optional classes that change the appearance of content-tabs-body. Class Description
Variables — public CSS variables for theming content-tabs-body. Opt-out:
If you wish to opt-out of these styles on a case-to-case basis, use property all: revert-layer. Valence.css ships this as .unstyled and .unstyled-all for the entire tree.
Variable Syntax Default
Aliases — @custom-selector synonyms so custom elements can share semantic styles. Element aliases match the host tag / .tag-* class; state aliases match attributes (nested under the element alias). Alias Kind Matches :--content-tabs-body element content-tabs-body, .tag-content-tabs-body

content-tabs-header

Attributes (2) Role — option is configurable, state is managed by the element (read-only), or hybrid which is both. Role Attribute Type Values Prop Default option tab-name
Pairs this header with the <content-tabs-body> sharing the same tab-name. Unset headers / bodies pair by position instead. tabName
string null
hybrid is-open
Open state. Click sets or toggles this depending on the parent's tab-type; set it directly to drive the tab programmatically. isOpen
boolean false
Provision (0) This provision property lives on the DOM node. Read / watch it with Quark’s prop("provision"), or listen for the neutron-provision event from app JS. Property Type
Events (1)
Dispatches — events that content-tabs-header fires and their default actions. Dispatch Default Action
A "default action" is subsequent logic executed by the element if e.preventDefault() is not synchronously called on the event.
Name content-tabs-header-opened
Whenever is-open is set. The parent <content-tabs> listens for this to close sibling headers.

Type ContentTabsHeaderOpenedEvent
Listeners — events that content-tabs-header listens for which will trigger subsequent actions. Listener Type Action
Commands — verbs content-tabs-header accepts as a command event (HTML Command API): from a <button command commandfor>, an <event-handler command-name>, or a CommandEvent. Command Action
Recognized Elements (0) Child or descendant elements are recognized by content-tabs-header and relevant to its functionality. Element Relationship Required
Styles (1)
Classes — optional classes that change the appearance of content-tabs-header. Class Description
Variables — public CSS variables for theming content-tabs-header. Opt-out:
If you wish to opt-out of these styles on a case-to-case basis, use property all: revert-layer. Valence.css ships this as .unstyled and .unstyled-all for the entire tree.
Variable Syntax Default
Aliases — @custom-selector synonyms so custom elements can share semantic styles. Element aliases match the host tag / .tag-* class; state aliases match attributes (nested under the element alias). Alias Kind Matches :--content-tabs-header element content-tabs-header, .tag-content-tabs-header
Release notes (1)

0.1.1

  • Ship only dist (and declared extras) in the npm tarball; drop build logs, tests and sources
View Source

Examples

File tabs

.file-tabs renders each header like a folder tab, joined to its body.

Multi-select accordion

tab-type="multi" lets any number of headers stay open — clicking one does not close the others. Also displayed are the tab headers as buttons, if using Valence.css.

Useful for building your own UI toggle systems.

Pair by tab-name

Give a header and body matching tab-name values to pair them regardless of their order in the DOM.

Beta. The Nucleus Stack is in beta for a few weeks until features are stabilized and optimized.

Thanks — we'll email you when it ships.

Something went wrong. Please try again.