MindeesUI

Layout Intelligence Layer

How MindeesUI components introspect their children and auto-adjust spacing, sizing, alignment, accessibility roles, and styling. Every rule is deterministic, documented, and overridable — never AI magic.

Layout Intelligence Layer (LIL)

The deterministic engine that makes "drop children in and it looks right" work in MindeesUI. Every behavior is a typed function of its inputs. Same children + same parent context = same output, always.

Principles

  1. Deterministic. No model. No probabilistic anything. Pure typed functions.
  2. Documented. Every rule is listed below and matched 1:1 to a test case.
  3. Overridable. Every inferred prop can be set explicitly. Override always wins.
  4. Tree-shakeable. Small modules; pay only for the heuristics you trigger.
  5. No runtime cost when idle. Heuristics run during parent render — never on UI thread, never in a worklet.

Slot / asChild

Radix-style prop-merging slot. <Slot {...parent}><Child /></Slot> returns the child with merged props — no extra wrapping View.

Prop familyMerge rule
onX event handlerschild first, then parent (parent skipped if evt.defaultPrevented)
stylearray ([parentStyle, childStyle]); RN flattens, child wins on conflict
className (web)concatenated
refboth forwarded via mergeRefs
any other propchild wins

Components opt in via asChild:

function Trigger({ asChild, ...rest }: AsChildProps<PressableProps>) {
  const Comp = asChild ? Slot : Pressable;
  return <Comp {...rest} />;
}

Tagged components

Every MindeesUI component carries a Symbol.for('@mindees/ui:component-tag') so parents can identify children even across module realms.

import { tagComponent, getComponentTag, isTaggedAs } from '@mindees/ui';

const MyButton = tagComponent(function MyButton() { /* ... */ }, 'Button');

getComponentTag(<MyButton />);      // 'Button'
isTaggedAs(<MyButton />, 'Button'); // true

Child introspection

flattenChildren collapses fragments and arrays; describeChildren returns positional slots { element, tag, index, isFirst, isLast }. Parents iterate these to round outer corners, hide last dividers, etc. — without re-rendering children.

import { describeChildren } from '@mindees/ui';
const slots = describeChildren(children);
// [{ element, tag, index, isFirst, isLast }, ...]

Intrinsic sizing

width and height accept IntrinsicSize = 'fill' | 'hug' | number | '${number}%':

InputMaps to
fill{ flexGrow: 1, flexShrink: 1, flexBasis: 0 }
hug{ flexShrink: 0 } (intrinsic content size)
numberwidth: N or height: N
${N}%width: 'N%' or height: 'N%'

Auto-spacing rules

Stack / HStack / VStack take a base gap token, then apply a per-pair rule for (prev, next):

RuleMultiplierTriggered by
tighter× 0.25Heading ↔ Caption / Label (eyebrow text)
tight× 0.5Text → Text (continuous prose)
base× 1anything else
loose× 1.5Heading → Text (hierarchy break); content → Button (trailing action)
looser× 2(reserved for section breaks)

Specialised contexts

Each compound parent publishes a typed context describing only what its children need:

ContextProvided byRead by
ButtonGroupContextButtonGroupButton, IconButton (corner-merging, size sync)
ListContextListListItem (density, dividers)
StackContextStack / HStack / VStackdirect children (axis-aware spacing)
FormFieldContextFormFieldInput, Label, HelperText (a11y wiring)
CardContextCardCard.Header / .Body / .Footer

Components without a wrapping parent fall back to sensible defaults — no missing-context throws.

Responsive + density

useResponsive(value) resolves a ResponsiveValue<T> (scalar or { xs?, sm?, md?, lg?, xl?, '2xl'? }) against the current width. resolveResponsive(value, width) is the pure-function variant for component-internal use.

const padding = useResponsive({ xs: 'sm', md: 'lg', xl: '2xl' });

A11y auto-wiring

  • FormField issues stable ids; useFormFieldA11y reads them for ready-to-spread accessibilityLabelledBy + accessibilityDescribedBy.
  • Compound display components emit grouping roles automatically.
  • Every animation gates on useReduceMotion.
  • Explicit overrides always win over inferred values.

On this page