The systemLumos 0.0.1

How components work

The conventions every Lumos component follows, so learning one teaches you the rest.

1. Every component takes render

Pass render={false} and the component and everything inside it is skipped. It is the escape hatch for anything conditional: an announcement bar that only appears during a sale, a section that only exists when the data has something in it.

astro
<Section render={posts.length > 0}>
  <Heading tag="h2">Latest writing</Heading>
</Section>

It reads better than the alternative and it keeps the markup flat, with the condition next to the thing it controls.

2. Empty components remove themselves

A component with nothing in it renders nothing at all: no empty <div> with padding, no stray gap in a flex column. This matters more than it sounds when content comes from a CMS: a card with no button does not leave a hole where the button would be.

3. Slots are named holes

Anything you put between a component's tags goes into its default slot. Some components have extra, named ones (a second column, a background layer), and you choose with the slot attribute.

astro
<ContentWrapper variant="columns">
  <Heading tag="h2">Left column</Heading>
  <Img slot="column2" src={photo} />
</ContentWrapper>

4. Impossible prop combinations do not typecheck

Props that only make sense together are grouped. Card takes an image on its cover and stacked variants, but not on the default one, and the editor tells you so as you type, rather than the image quietly not appearing.

The same idea shows up in Grid, where the column-count props belong to the columns variant and the width props belong to autofit and autofill, and in Button, where href belongs to a link and type belongs to a button.

5. Mistakes warn instead of breaking

Where a wrong value cannot be caught by types (a number out of range, a combination that has no effect), the component warns in the terminal during development and falls back to its default. It never throws, and the warnings never reach the built site.

text
[lumos] <ContentWrapper variant="columns"> ignores mediaOpacity;
        only the card variant has a visual to fade.

If something is not doing what you expect, the terminal running npm run dev is the first place to look.

Passing classes and attributes

Every component merges a class you pass with its own, and forwards any other HTML attribute to its root element. So a utility class, an id, a data- attribute or an aria- attribute all work exactly as they would on a plain element.

astro
<Section id="pricing" class="min-height-screen" data-analytics="pricing">

Writing your own

Add your own components in src/components alongside the framework's, and follow the same conventions: take render, put your styles in @layer components, read values from tokens, name classes block_element with an underscore, and prefix a custom property that is the component's own business with --_. A component that behaves like the others is one nobody has to learn twice.