Vitus Labs
Elements

Element

Core 3-section flex layout component — the foundational building block.

Element is the primary building block of the UI system. It renders a flex container with three optional sections: beforeContent, content (children), and afterContent. When only content exists, unnecessary nesting is bypassed for optimal DOM structure.

Peer dependencies: @vitus-labs/core, @vitus-labs/unistyle

Basic Usage

import { Element } from '@vitus-labs/elements'

// Simple content
<Element tag="p" label="Hello World" />

// Button with icon and label
<Element
  tag="button"
  beforeContent={<Icon name="star" />}
  gap={8}
  alignX="center"
  alignY="center"
>
  Favorite
</Element>

// Three-section layout
<Element
  beforeContent={<Avatar />}
  afterContent={<Badge count={5} />}
  gap={12}
  alignX="spaceBetween"
  alignY="center"
>
  <span>John Doe</span>
</Element>

Content Props

Content is resolved with this priority: children > content > label

PropTypeDescription
childrenReactNodeHighest priority content
contentContentAlternative to children (supports components, elements, render functions)
labelContentFallback content
beforeContentContentRendered before main content
afterContentContentRendered after main content

The Content type accepts anything the render() utility from @vitus-labs/core handles: React elements, component types (created via createElement), render functions ((props) => ReactNode), primitives, arrays, and fragments.

DOM Structure

Simple Element (no beforeContent/afterContent)

When no side content is provided, Element renders a single wrapper with content directly inside — no unnecessary nesting:

<div class="vl-..." style="display: inline-flex; ...">
  <!-- children / content / label -->
</div>

Direction defaults to contentDirection (or direction fallback), alignment uses contentAlignX/contentAlignY.

Complex Element (has beforeContent or afterContent)

When side content is present, three inner <div> (or <span> for inline tags) slots are rendered:

<div class="vl-..." style="display: inline-flex; flex-direction: row; ...">
  <div class="..." style="..."><!-- beforeContent --></div>
  <div class="..." style="flex: 1; ..."><!-- children / content / label --></div>
  <div class="..." style="..."><!-- afterContent --></div>
</div>

The outer wrapper uses direction/alignX/alignY, while each slot has its own direction/alignment props.

Layout Props

PropTypeDefaultDescription
tagHTMLTags'div'HTML element to render
blockboolean (responsive)flex (true) vs inline-flex (false)
directionDirection (responsive)'inline'Main flex direction
contentDirectionDirection (responsive)'rows'Children wrapper direction
beforeContentDirectionDirection (responsive)'inline'beforeContent wrapper direction
afterContentDirectionDirection (responsive)'inline'afterContent wrapper direction
gapnumber (responsive)Spacing between sections
alignXAlignX (responsive)'left'Horizontal alignment
alignYAlignY (responsive)'center'Vertical alignment
contentAlignX / contentAlignYAlignment (responsive)Children wrapper alignment
beforeContentAlignX / beforeContentAlignYAlignment (responsive)beforeContent alignment
afterContentAlignX / afterContentAlignYAlignment (responsive)afterContent alignment
equalColsboolean (responsive)Equal width/height for all sections
equalBeforeAfterbooleanSet both sidebars to the same size

Direction Values

ValueCSS
'inline'flex-direction: row
'rows'flex-direction: column
'reverseInline'flex-direction: row-reverse
'reverseRows'flex-direction: column-reverse

Alignment Values

AlignX: 'left' | 'center' | 'right' | 'spaceBetween' | 'spaceAround' | 'block'

AlignY: 'top' | 'center' | 'bottom' | 'spaceBetween' | 'spaceAround' | 'block'

Styling Props

PropTypeDescription
cssExtendCssRoot element styles
contentCssExtendCssChildren wrapper styles
beforeContentCssExtendCssbeforeContent wrapper styles
afterContentCssExtendCssafterContent wrapper styles

HTML Props

PropTypeDescription
tagstringHTML tag name
innerRefRefAlternative ref (avoids naming conflicts)
dangerouslySetInnerHTML{ __html: string }Raw HTML injection
All standard HTML attributesonClick, className, style, aria-, data-, etc.

Responsive Layout

// Stack on mobile, inline on desktop
<Element
  direction={['rows', 'inline']}
  alignX={['center', 'left']}
  gap={[8, 16]}
>
  {children}
</Element>

// Breakpoint object
<Element
  gap={{ xs: 8, md: 12, lg: 16, xl: 20 }}
  alignX={{ xs: 'center', md: 'left' }}
>
  {children}
</Element>

Special Handling

Void Elements

For self-closing tags (input, img, br, hr, embed, etc.), Element renders just the wrapper without content sections:

<Element tag="input" type="text" placeholder="Enter text" />
// Renders: <input type="text" placeholder="Enter text" />

Inline Elements

When the tag is inline (span, a, button, label), content wrappers use <span> instead of <div> to maintain valid HTML nesting.

Browser Flexbox Fix

For <button>, <fieldset>, and <legend> (which don't natively support flex), Element applies a two-layer DOM structure to achieve proper flex behavior.

equalBeforeAfter

Measures both side sections and sets them to the larger dimension via useLayoutEffect. Useful for centered content with unequal side elements:

<Element equalBeforeAfter alignX="center">
  <Element beforeContent={<BackButton />}>
    <Text tag="h1">Page Title</Text>
  </Element>
</Element>
// Both beforeContent and afterContent get equal width
// so the title is perfectly centered

Uses getBoundingClientRect() and direct style manipulation to set both slots to Math.max(beforeSize, afterSize). Recalculates when equalBeforeAfter, beforeContent, afterContent, or direction change.

Ref Forwarding

Element supports both ref (via forwardRef) and the innerRef prop:

const ref = useRef(null)
<Element ref={ref} tag="button">Click</Element>
// or
<Element innerRef={ref} tag="button">Click</Element>

When equalBeforeAfter is enabled, an internal ref is used for measurement. Both internal and external refs are merged via a callback ref to ensure both function correctly.

Reserved Props

These props are consumed by Element and not forwarded to the DOM:

innerRef, tag, block, label, children, beforeContent, afterContent,
equalCols, vertical, direction, alignX, alignY, css, contentCss,
beforeContentCss, afterContentCss, contentDirection, contentAlignX,
contentAlignY, beforeContentDirection, beforeContentAlignX,
beforeContentAlignY, afterContentDirection, afterContentAlignX,
afterContentAlignY

All other props (onClick, className, style, aria-, data-, etc.) are forwarded to the root DOM element.

Static Properties

Element.displayName    // '@vitus-labs/elements/Element'
Element.pkgName        // '@vitus-labs/elements'
Element.VITUS_LABS__COMPONENT  // '@vitus-labs/elements/Element'

API Reference

Prop

Type

On this page