@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; }

web-authn

Passkey register / authenticate with HTML. Pair it with Quark to render the result.

<web-authn start-method="register" options-url="/api/registration-options" verify-url="/api/users">
    <form>
        <button type="submit">One click sign up!</button>
    </form>
</web-authn>

Features

  • Provides data Use Quark to render the verify response
  • Full ceremony Fetches options, runs the browser's WebAuthn prompt, then verifies — one element
  • Register or authenticate start-method picks the ceremony
  • Submit command --submit starts the ceremony programmatically — for forms outside the DOM subtree, or buttons outside the <form>
  • Chainable web-authn-success fires like any {tag}-success event — chain a redirect or next step
  • Highly configurable Headers, credentials, redirect, etc

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/web-authn@0.1.5/dist/index.umd.min.js"></script>
npm install @excom/web-authn
HTML Imports JS / CSS Imports
<!-- import path to `node_modules` will depend on your build setup -->
<script type="module" src="/node_modules/@excom/web-authn"></script>
<link rel="stylesheet" href="/node_modules/@excom/web-authn">
import "@excom/web-authn";
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
./index
  • types: ./dist/index.d.ts
  • import: ./dist/index.js
  • default: ./dist/index.js
./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.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
./web-authn
  • types: ./dist/web-authn.d.ts
  • import: ./dist/web-authn.js
  • default: ./dist/web-authn.js
./web-authn.js
  • types: ./dist/web-authn.d.ts
  • import: ./dist/web-authn.js
  • default: ./dist/web-authn.js
./web-authn.min
  • types: ./dist/web-authn.d.ts
  • import: ./dist/web-authn.min.js
  • default: ./dist/web-authn.min.js
./web-authn.min.js
  • types: ./dist/web-authn.d.ts
  • import: ./dist/web-authn.min.js
  • default: ./dist/web-authn.min.js
./web-authn.umd.min
  • types: ./dist/web-authn.d.ts
  • default: ./dist/web-authn.umd.min.js
./web-authn.umd.min.js
  • types: ./dist/web-authn.d.ts
  • default: ./dist/web-authn.umd.min.js

Depends on @simplewebauthn/browser for the actual WebAuthn calls (startRegistration / startAuthentication). You can use any server-side library to handle the WebAuthn requests, but it is recommended to use the counterpart library, @simplewebauthn/server, since they seamlessly understand the same contract.

Usage

<web-authn options-url="/api/webauthn/register/options"
  verify-url="/api/webauthn/register/verify" start-method="register">
  <form>
    <input name="username" required>
    <button type="submit">Register passkey</button>
  </form>
</web-authn>

On submit: options-url is fetched for ceremony options, the browser's native passkey prompt runs (@simplewebauthn/browser), and the resulting credential is posted to verify-url. Use start-method="authenticate" for sign-in instead of registration.

Hook the lifecycle state with CSS:

web-authn[is-loading] { /* show loading spinner */ }
web-authn[is-error]::before { content: "An error occurred." }

Or Quark:

web-authn[is-success] {
  $res: prop("provision").body;
  span { content: $res.verified; }
}

Chain a next step off success the same way you would for any <super-form> or <provider-fetch>:

<event-handler listen-for="web-authn-success" fire-event="onboarding-step-complete">
  <web-authn options-url="/api/webauthn/register/options"
    verify-url="/api/webauthn/register/verify" start-method="register">
    <form><input name="username"><button type="submit">Register</button></form>
  </web-authn>
</event-handler>

Examples

Authenticate

API Reference

web-authn

Attributes (15) Role — option is configurable, state is managed by the element (read-only), or hybrid which is both. Role Attribute Type Values Prop Default option api-method
HTTP method. Always uppercased before the request is sent. apiMethod fetchable-element
string "GET"
option api-url
Endpoint URL. When the request has no body, the JSON payload (from form-ref or custom doFetch() args) is merged in as query params instead. apiUrl fetchable-element
string ""
option fetch-credentials
RequestInit.credentials mode. fetchCredentials fetchable-element
string "omit" | "same-origin" | "include" "include"
option fetch-redirect
RequestInit.redirect mode. Unset defers to the browser default (follow). fetchRedirect fetchable-element
string "follow" | "error" | "manual" null
option form-ref
CSS selector for the <form> to intercept. Must be a descendant to be heard directly — point elsewhere and invoke the --submit command instead. formRef
string <CSS Selector> ":scope form"
option form-ref
CSS selector for a <form> to source the request from — its action (URL), method, enctype (Content-Type), and field values (as the JSON payload) all take priority over the matching attributes below. Omit to build the request entirely from attributes / custom doFetch() args. formRef fetchable-element
string <CSS Selector> null
option has-body
Force a request body even for methods that don't imply one (GET / HEAD). Already implied for POST / PUT / PATCH. hasBody fetchable-element
boolean false
option header-accept
Accept request header. headerAccept fetchable-element
string "application/json"
option header-cache-control
Cache-Control request header. Unset by default (browser default caching applies). headerCacheControl fetchable-element
string null
option header-content-type
Content-Type request header. Dropped entirely when the request has no body. headerContentType fetchable-element
string "application/json"
option start-method
Which WebAuthn ceremony to run. startMethod
string "register" | "authenticate" null
option verify-url
Endpoint that verifies the credential produced by the browser prompt (your server's verifyRegistrationResponse / verifyAuthenticationResponse). Receives the credential as the request body. verifyUrl
string <URL> null
state is-error
The most recent request failed (status 400 or above, network error, or a thrown error other than AbortError). Fires with the error event. isError fetchable-element
boolean false
state is-loading
A request is currently in flight. isLoading fetchable-element
boolean false
state is-success
The most recent request resolved successfully. Mutually exclusive with is-loading and is-error. isSuccess fetchable-element
boolean false
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
Response payload on success, or error payload on failure. Success shape: { status, statusText, ok, headers, url, redirected, bodyUsed, type, body }. Failure shape is either that same response shape (server responded with an error status) or { message, stack } (request never completed). Not reflected as an attribute. provision fetchable-element
FetchResponse
Events (6)
Dispatches — events that web-authn 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 web-authn-error
Dispatched when the request fails — status 400 or above, network error, or a thrown error. event.detail is the error payload (see provision). Not dispatched for aborted requests. fetchable-element

Type FetchableErrorEvent
Name web-authn-loading
Dispatched immediately before the request is sent. fetchable-element

Type FetchableLoadingEvent
Name web-authn-submit
Internal — dispatched once the browser ceremony (register or authenticate) resolves, just before the verify-url fetch runs. The credential is the request body. Default action calls doFetch().

Type WebAuthnSubmitEvent
Calls doFetch([url, requestInit]) with the event's detail (the verify-url request).
Name web-authn-success
Dispatched when the request resolves successfully. event.detail is the parsed response (see provision). fetchable-element

Type FetchableSuccessEvent
Listeners — events that web-authn listens for which will trigger subsequent actions. Listener Type Action submit WebAuthnNativeSubmitEvent
The default action of the <form> matched by form-ref; prevented, then starts the ceremony.
Commands — verbs web-authn accepts as a command event (HTML Command API): from a <button command commandfor>, an <event-handler command-name>, or a CommandEvent. Command Action --submit Starts the ceremony programmatically (<button command="--submit" commandfor="…">) — the only option when form-ref points to a form that isn't a descendant.
Recognized Elements (0) Child or descendant elements are recognized by web-authn and relevant to its functionality. Element Relationship Required
Styles (0)
Classes — optional classes that change the appearance of web-authn. Class Description
Variables — public CSS variables for theming web-authn. 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
Release notes (2)

0.1.4

  • A failed ceremony or options request now clears is-loading and sets is-error with the message in provision

0.1.1

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

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.