Skip to main content

Primitives

Fluid Primitives provides headless, accessible primitives built on Zag.js state machines. These handle behavior and accessibility; you handle styling.

What's a Primitive?

A primitive is an unstyled component part with built-in:

Primitives use the primitives namespace:

<primitives:accordion.root>
    <primitives:accordion.item value="item-1">
        <primitives:accordion.trigger>Section 1</primitives:accordion.trigger>
        <primitives:accordion.content>Content here</primitives:accordion.content>
    </primitives:accordion.item>
</primitives:accordion.root>

Primitive vs Component

Primitives (primitives:*) are the raw building blocks. They're verbose but give you full control.

Components (ui:*) are your design system wrappers around primitives. They add your styling, simplify the API, and enforce consistency.

Example tooltip primitive anatomy:

<primitives:tooltip.root>
    <primitives:tooltip.trigger />
    <primitives:tooltip.positioner>
        <primitives:tooltip.content>
            <primitives:tooltip.arrow />
        </primitives:tooltip.content>
    </primitives:tooltip.positioner>
</primitives:tooltip.root>

Your simplified component might combine parts:

<ui:tooltip.root>
    <ui:tooltip.trigger />
    <ui:tooltip.content />
    <!-- Wraps positioner + content + arrow -->
</ui:tooltip.root>

Building Components from Primitives

Create wrapper components that use primitives internally. Here's a typical pattern:

ui/Tooltip/Root.html:

<ui:useProps name="primitives:tooltip.root" />

<primitives:tooltip.root spreadProps="{true}">
    <f:slot />
</primitives:tooltip.root>

<vite:asset entry="EXT:my_ext/Resources/Private/Components/ui/Tooltip/tooltip.entry.ts" />

ui/Tooltip/Trigger.html:

<ui:useProps name="primitives:tooltip.trigger" />

<primitives:tooltip.trigger class="{class}" spreadProps="{true}">
    <f:slot />
</primitives:tooltip.trigger>

ui/Tooltip/Content.html:

<ui:useProps name="primitives:tooltip.content" />

<primitives:tooltip.positioner>
    <primitives:tooltip.content class="tooltip-content {class}" spreadProps="{true}">
        <f:slot />
        <primitives:tooltip.arrow class="tooltip-arrow" />
    </primitives:tooltip.content>
</primitives:tooltip.positioner>

Now you can use your styled, simplified API:

<ui:tooltip.root>
    <ui:tooltip.trigger>Hover me</ui:tooltip.trigger>
    <ui:tooltip.content>Styled tooltip with arrow</ui:tooltip.content>
</ui:tooltip.root>

Key Concepts

ui:useProps

Imports prop definitions from another component. This ensures your wrapper accepts the same props as the primitive without redeclaring them.

spreadProps="{true}"

Passes all inherited props from ui:useProps down to the primitive. Without this, you'd need to manually pass each prop.

<!-- Import all props -->
<ui:useProps name="primitives:accordion.root" />

<!-- Import specific props -->
<ui:useProps name="primitives:accordion.root" props="{0: 'multiple', 1: 'defaultValue'}" />

JavaScript Entry Files

Each primitive needs client-side initialization. Load it in the root template so it's only included when the component is used:

<vite:asset entry="path/to/component.entry.ts" />

<!-- Or with core asset ViewHelper -->
<f:asset.script identifier="my-component" src="path/to/component.js" />

Scaffolding with the CLI

Use the TYPO3 CLI to scaffold components:

# List available primitives
typo3 ui:list

# Add a component to your project
typo3 ui:add accordion

This generates component files with basic Tailwind styling as a starting point.

Available Primitives

See the Components section for all available primitives with examples and API documentation.

Registry Limitations

The CLI scaffolding provides a starting point, not a complete design system. You'll need to:

It's inspired by shadcn/ui's approach: copy the code, own it, customize it.