v2.0.0

What DryUI owns

DryUI improves consistency, component correctness, and accessibility. It does not remove route complexity, domain orchestration, or state isolation work.

Presentation system

Use DryUI for fields, summaries, cards, alerts, transitions, and control semantics. Let your route code own session state, planner normalization, and cross-step orchestration.

One layout rule

All layout is scoped CSS grid. Use Container for constrained width and @container-based layout rules for responsive behavior.

Lookup-first workflow

Start with dryui info or dryui compose, then use check if your editor exposes DryUI MCP. Without MCP, rely on the project build, tests, and the @dryui/lint preprocessor to catch drift.

Recommended workflow

The shortest stable path is lookup, implement, validate.

1. Lookup

Use dryui info or dryui compose to confirm simple vs compound shape, required parts, bindables, and the canonical usage snippet. If MCP is available, ask is the equivalent surface.

2. Implement

Build the route with raw CSS grid, semantic tokens, and DryUI components for controls and stateful surfaces.

3. Validate

Run check on the component, theme CSS, directory, or workspace when DryUI MCP is available so layout drift, compound misuse, and accessibility regressions get caught before they spread. Without MCP, rebuild and run the normal test suite after wiring the change.

State-heavy planners

Dependent-field flows still need disciplined route state. Normalize first, then render DryUI.

This pattern keeps dependent selects honest: when the parent value changes, derived options update and stale child selections are cleared. DryUI owns the field surface; your route code owns the state transition.

svelte
<script lang="ts">
  let country = $state('');
  let airport = $state('');

  const airportOptions = $derived(getAirports(country));

  $effect(() => {
    if (!airportOptions.some((option) => option.value === airport)) {
      airport = '';
    }
  });
</script>

<div class="planner">
  <Field.Root>
    <Label>Country</Label>
    <Select.Root bind:value={country}>
      <Select.Trigger><Select.Value placeholder="Choose country" /></Select.Trigger>
      <Select.Content>{/* items */}</Select.Content>
    </Select.Root>
  </Field.Root>

  <Field.Root>
    <Label>Airport</Label>
    <Select.Root bind:value={airport} disabled={!country}>
      <Select.Trigger><Select.Value placeholder="Choose airport" /></Select.Trigger>
      <Select.Content>{/* filtered items */}</Select.Content>
    </Select.Root>
  </Field.Root>
</div>

<style>
  .planner {
    display: grid;
    gap: var(--dry-space-4);
  }
</style>