Marigold
Marigold
Component Principlesnew
Accessibility
Design Tokens
Elevation
Layouts
Icons
Form Fields
Spacingalpha
Foundations

Component Principles

The core ideas behind how Marigold components are designed, styled, and composed.

Marigold components share a consistent set of design principles that shape their API. Understanding these principles helps you use the system effectively and avoid common pitfalls. This page introduces the core pillars: accessibility, theming, composition, layout, and shared form field patterns.

Accessible by Default

Every Marigold component is built on top of React Aria, a library of accessible UI primitives. This means keyboard navigation, focus management, and ARIA attributes are handled for you out of the box.

We rename some React Aria props for a cleaner developer experience:

React AriaMarigold
isDisableddisabled
isPendingloading
isReadOnlyreadOnly
isRequiredrequired

This gives you a solid foundation, but accessibility is a shared responsibility. You still need to provide proper labels, manage ARIA live regions for dynamic content, and test with assistive technologies.

For the full picture, see the Accessibility page.

Theming

Marigold components are unstyled by default. They provide structure and behavior, but their visual appearance comes entirely from a theme. A theme is a collection of style definitions that covers colors, typography, spacing, and visual variations for each component. It is passed to the MarigoldProvider at the root of your application. Every component within it automatically picks up its styles from there.

Because styling is owned by the theme, components don't accept className or style props. Instead, their appearance is controlled through two props: variant and size.

  • variant defines the visual intent of a component, such as primary, secondary, ghost, or destructive.
  • size controls the physical dimensions and density, such as default, small, or large.

These props map to styles defined in the theme. When you write variant="ghost", you're selecting a set of pre-defined styles that the theme author has designed to work together.

import { Eye, Pencil, Trash2 } from 'lucide-react';import { Inline, Menu } from '@marigold/components';export default () => (  <Inline space="group" alignX="center">    <Menu label="Actions" variant="default" size="default">      <Menu.Section title="Event">        <Menu.Item id="view">          <Eye />          View Details        </Menu.Item>        <Menu.Item id="edit">          <Pencil />          Edit Event        </Menu.Item>      </Menu.Section>      <Menu.Section title="Danger Zone">        <Menu.Item id="cancel" variant="destructive">          <Trash2 />          Cancel Event        </Menu.Item>      </Menu.Section>    </Menu>    <Menu label="Actions" variant="ghost" size="small">      <Menu.Section title="Event">        <Menu.Item id="view">          <Eye />          View Details        </Menu.Item>        <Menu.Item id="edit">          <Pencil />          Edit Event        </Menu.Item>      </Menu.Section>      <Menu.Section title="Danger Zone">        <Menu.Item id="cancel" variant="destructive">          <Trash2 />          Cancel Event        </Menu.Item>      </Menu.Section>    </Menu>  </Inline>);

How it Works

The chain from prop to rendered style looks like this:

  1. You pass variant and size to a component.
  2. The component looks up the matching styles from the current theme.
  3. The theme contains style definitions for each component, written with cva (class variance authority).
  4. Based on your variant and size, the matching Tailwind CSS classes are selected and applied to the component.

For example, a simplified theme entry for a Menu trigger button looks like this:

// themes/theme-rui/src/components/Menu.styles.ts
export const Menu = {
  button: cva({
    base: ['...base styles'],
    variants: {
      variant: {
        default: '...surface styles with border',
        ghost: '...transparent with hover background',
      },
      size: {
        default: 'h-button p-squish-relaxed',
        small: 'h-button-small px-3',
      },
    },
  }),
};

If the available variants don't cover your use case, you can extend the theme using the extendTheme function. This lets you add new variants and sizes to any component without modifying the design system itself.

Composition

Complex components in Marigold are assembled from smaller building blocks using dot notation. Instead of configuring everything through a single component's props, you compose the component from its parts.

Take the Menu component: it's made up of Menu.Item for individual actions and Menu.Section for grouping related items with a heading.

<Menu label="Actions">
  <Menu.Section title="Event">
    <Menu.Item id="view">View Details</Menu.Item>
    <Menu.Item id="edit">Edit Event</Menu.Item>
  </Menu.Section>
  <Menu.Section title="Danger Zone">
    <Menu.Item id="cancel" variant="destructive">
      Cancel Event
    </Menu.Item>
  </Menu.Section>
</Menu>

This pattern has several advantages:

  • Readability: the structure of the UI is visible in the JSX tree.
  • Flexibility: you control the order, nesting, and content of each part.
  • Type safety: each sub-component has its own typed props, so you get autocompletion and validation.

You'll find this pattern across the design system. For example, Dialog uses Dialog.Trigger, Dialog.Title, Dialog.Content, and Dialog.Actions. Table uses Table.Header, Table.Body, Table.Row, and Table.Cell.

Layout

Components in Marigold never set their own outer spacing. A Button doesn't know or care whether it has margin around it. Instead, layout is always the responsibility of a parent layout component.

Marigold provides layout primitives like Inline, Stack, Grid, and Columns that accept props such as space and alignX to control how children are arranged.

import { Button, Inline } from '@marigold/components';export default () => (  <Inline space="related" alignX="center" alignY="center">    <Button variant="primary">Save Changes</Button>    <Button variant="secondary">Cancel</Button>  </Inline>);

In the example above, the Inline component handles the horizontal arrangement. The space prop controls the gap between the buttons using a semantic spacing token (related), and alignX centers them horizontally.

All layout components share this same set of props. Beyond space and alignX, you will find alignY for cross-axis alignment. The space prop accepts semantic tokens that describe the relationship between elements rather than a fixed pixel value. For the full token vocabulary and how to choose the right one, see the Spacing page.

This separation has an important benefit: components remain portable. The same Button works in a dialog footer, a toolbar, or a form without any layout adjustments.

Need something that doesn't exist?

If you find yourself reaching for wrapper elements or custom CSS to achieve a layout, it might indicate a missing feature in the design system. Get in touch with the design system team so we can help.

For a deeper look at how spacing works, check out the Spacing and Layouts pages.

Form Fields

Marigold's form components share a uniform API. Once you learn how one form field works, the same knowledge transfers to all of them.

Every form field supports the same anatomy through three props:

  • label provides a visible label associated with the input.
  • description adds help text below the field for additional context.
  • errorMessage replaces the description when the field is in an error state.

They also share a consistent set of state props:

  • disabled prevents interaction with the field.
  • readOnly allows the value to be read but not changed.
  • required marks the field as mandatory.
  • error puts the field into an error state, triggering the error message.

Additionally, all form fields accept a width prop to control their sizing, supporting both fixed values and fractional widths like 1/2 or 1/3.

For a complete guide to form field anatomy, width control, states, and validation, see the Form Fields page.

Related

Spacing

How relational and inset spacing tokens create consistent rhythm.

Layouts

Atomic layout components and the philosophy behind them.

Form Fields

Shared anatomy, field states, width control, and validation for all form components.

Accessibility

How accessibility is built into the foundation of every component.

Design Tokens

The shared values that define colors, spacing, typography, and more.

extendTheme

Add custom variants and sizes to any component without modifying the design system.
Last update: 2 minutes ago

Release Phases

Understand the phases that drive component stability, from Alpha to Stable

Accessibility

A guide to accessibility best practices for making your product usable for all.

On this page

Accessible by DefaultThemingCompositionLayoutForm FieldsRelated