Astro
Use the components in Astro two ways: React islands that mount in the browser, and zero-framework custom elements loaded by a page script.
Set up
npm create astro@latest my-site
cd my-site
npx astro add react # only for React islands
npm install @kyralabs/m3exp-web @kyralabs/m3e-tokens @kyralabs/m3e-elements
@kyralabs/m3e-elements is needed only for its per-element entries (one import per
element, below). The umbrella's @kyralabs/m3exp-web/elements registers all of them
at once.
Load the styles in a layout
Import the CSS in the frontmatter of a layout every page uses. Astro bundles it and adds it to each page.
---
// src/layouts/Layout.astro
import '@kyralabs/m3exp-web/css/index.css';
import '@kyralabs/m3e-tokens/material-symbols.css';
---
<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 app</title>
</head>
<body>
<slot />
</body>
</html>
Markup with m3e-css classes, such as the link styled as a button below, can go straight into
any .astro template: it is plain HTML and CSS, so it renders at build time and
needs no JavaScript.
React islands
Write a React component and mount it with client:only="react". The components
render in the browser only (DECISIONS D6), so do not use client:load,
client:idle or client:visible: those render the component on the
server first.
// src/components/LikeButton.tsx
import { useState } from 'react';
import { Icon, IconButton } from '@kyralabs/m3exp-web/react';
export default function LikeButton() {
const [liked, setLiked] = useState(false);
return (
<IconButton variant="tonal" aria-label="Like" isSelected={liked} onChange={setLiked}>
<Icon name="favorite" />
</IconButton>
);
}
---
// src/pages/index.astro
import Layout from '../layouts/Layout.astro';
import LikeButton from '../components/LikeButton.tsx';
---
<Layout>
<h1>Islands</h1>
<a class="m3e-button m3e-button--tonal m3e-button--size-m" href="/elements/">Elements page</a>
<LikeButton client:only="react" />
</Layout>
A client:only island leaves its spot empty until its script runs. To show
something meanwhile, or to readers without JavaScript, give it static m3e-css markup in its
fallback slot: <div slot="fallback">…</div>.
Custom elements, no framework
Import each element's entry in a page <script>: Astro bundles it, and the page
loads only the elements it uses. Element events are typed, so
event.detail needs no cast.
---
// src/pages/elements.astro
import Layout from '../layouts/Layout.astro';
---
<Layout>
<h1>No framework</h1>
<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>
</Layout>
<script>
import '@kyralabs/m3e-elements/ripple.js';
import '@kyralabs/m3e-elements/menu.js';
import '@kyralabs/m3e-elements/loading-indicator.js';
document.querySelector('m3e-menu')?.addEventListener('action', (event) => {
console.log('picked', event.detail.value);
});
</script>
Elements render their markup in the browser when their entry loads. Until then their children
are data (the menu items above stay hidden), and the page's static parts are already there.
ripple.js adds the press ripple to every plain-HTML button, chip, card and list
item on the page; see ripple for plain HTML.
Keep pages light
-
Import only the components a page shows: the token stylesheets plus one
@kyralabs/m3e-css/<component>.csseach (install@kyralabs/m3e-css), as in the overview. -
Subset the icon font with
m3e-icons; see fonts and icons.
This site is an Astro site built this way; its source is apps/site in the
repository.