Zubra docs

4. Composition and slots#

Components using components#

Import a component and use its tag:

<script>
  import './user-avatar.zb'

  let people = [{ id: 1, name: "Ada", online: true }]
</script>
<people-list>
  <zb-each items={people} as="person" key={person.id}>
    <user-avatar name={person.name} online={person.online}></user-avatar>
  </zb-each>
</people-list>

The compiler knows the child's props, so a typo (nmae={…}), a wrong type or a missing required prop is an error at build time, in the parent's file. Values go in as properties, so objects and arrays arrive as they are.

Slots#

Content placed inside an element goes to its slots:

<!-- info-card.zb -->
<script>
  import { hasSlot } from '@zubra/runtime'

  const hasFooter = hasSlot('footer')
</script>
<info-card heading="">
  <h3>{heading}</h3>
  <zb-slot><p>Nothing here yet.</p></zb-slot>
  <zb-if test={hasFooter}>
    <footer><zb-slot name="footer"></zb-slot></footer>
  </zb-if>
</info-card>
<info-card heading="Plans">
  <p>Three plans, billed monthly.</p>
  <a slot="footer" href="/pricing">Compare plans</a>
</info-card>
  • <zb-slot> is the default slot; <zb-slot name="footer"> takes children with slot="footer". A slot's own content is its fallback, shown while nothing is given.
  • hasSlot(name) says whether content was given, and follows it as content comes and goes. Use it to leave out markup that only makes sense around a used slot.
  • Slotted content stays live: when React or Vue update the children they passed, the slot follows.

Light DOM or shadow DOM#

Components render into light DOM by default: their markup is part of the page, scoped styles keep their CSS to themselves, and page-level CSS (and Tailwind) still reaches in. Add shadow to the host tag for a shadow root: full style isolation, and native <slot>s.

Sharing state between components#

createContext, provide and inject pass a value down the tree without props, following the community Context Protocol (so they also work with Lit's context). For app-wide state, a module that exports state() signals works too: every component that reads one updates when it changes.

Ready-made components, and publishing your own#

npx zubra add modal-dialog copies a component from the registry into your project: twenty-five accessible components, from form controls and charts to tabs, menus and a dialog. When yours are ready to share, npx zubra build --lib src/components makes them a package that works in every framework (the README's "Sharing components" section).

Next: Forms

Edit this page on GitHub · Zubra 5.0.3