Skip to content

Plain HTML and CDN

Use the custom elements and CSS classes on a page with no framework and no bundler, loading the scripts from a CDN.

Elements from a CDN

Each custom element is one ES module with no dependencies outside the package, so a <script type="module"> tag can load it straight from a CDN. unpkg follows the package's export map, so the URLs match the import paths; pin a version:

<script type="module" src="https://unpkg.com/@kyralabs/[email protected]/menu.js"></script>

On CDNs that serve files by path, such as jsDelivr, use the file the export points to: https://cdn.jsdelivr.net/npm/@kyralabs/[email protected]/dist/menu/index.js. Module scripts run after the document is parsed; elements render when their module loads.

The stylesheets

The token stylesheets are self-contained and load from a CDN too:

<link rel="stylesheet" href="https://unpkg.com/@kyralabs/[email protected]/fonts.css" />
<link rel="stylesheet" href="https://unpkg.com/@kyralabs/[email protected]/tokens.css" />
<link rel="stylesheet" href="https://unpkg.com/@kyralabs/[email protected]/theme.css" />
<link rel="stylesheet" href="https://unpkg.com/@kyralabs/[email protected]/material-symbols.css" />

The component stylesheets do not load from a CDN yet. The m3e-css files import their component tokens by package name (@import "@kyralabs/m3e-tokens/…"), which only a bundler resolves; a browser looks for those files next to the stylesheet and finds nothing, so components render unstyled. A self-contained bundle is tracked in issue #106.

Until then, bundle the CSS once and host the result with your page. One esbuild command resolves the imports and copies the font files:

npm install @kyralabs/m3exp-web esbuild
npx esbuild --bundle node_modules/@kyralabs/m3exp-web/dist/css/index.css \
  --outfile=m3e/m3e.css --loader:.woff2=file --minify

m3e/m3e.css then holds the fonts, the tokens, the baseline theme and every component class, and the font files sit beside it. To bundle fewer components, point esbuild at a file of your own that imports the token stylesheets and the @kyralabs/m3e-css/<component>.css files you use.

A complete page

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="color-scheme" content="light dark" />
    <title>My page</title>
    <!-- Fonts, tokens, theme and component classes: bundled once, see below -->
    <link rel="stylesheet" href="./m3e/m3e.css" />
    <!-- Icons -->
    <link rel="stylesheet" href="https://unpkg.com/@kyralabs/[email protected]/material-symbols.css" />
    <!-- One module per element -->
    <script type="module" src="https://unpkg.com/@kyralabs/[email protected]/ripple.js"></script>
    <script type="module" src="https://unpkg.com/@kyralabs/[email protected]/menu.js"></script>
    <script type="module" src="https://unpkg.com/@kyralabs/[email protected]/loading-indicator.js"></script>
  </head>
  <body>
    <button type="button" class="m3e-button m3e-button--filled m3e-button--size-m">
      <span class="m3e-icon" aria-hidden="true">save</span>
      Save
    </button>
    <button id="more" type="button" class="m3e-icon-button" aria-label="More options">
      <span class="m3e-icon" aria-hidden="true">more_vert</span>
    </button>
    <m3e-menu anchor="more">
      <m3e-menu-item value="edit" icon="edit">Edit</m3e-menu-item>
      <m3e-menu-item value="share" icon="share">Share</m3e-menu-item>
    </m3e-menu>
    <m3e-loading-indicator contained aria-label="Loading"></m3e-loading-indicator>
  </body>
</html>

The button needs no script: m3e-css draws it, and ripple.js adds the press ripple. The menu and the loading indicator are elements. The page reads its color scheme from the system; add data-theme="dark" or data-theme="light" to <html> to force one (theming).

For production, ship only the icons you use: material-symbols.css loads a font of several megabytes. See fonts and icons.