# How Claude HUD Validates and Applies Element Order in the Expanded Layout

> Learn how Claude HUD validates and applies element order in its expanded layout. Discover the `validateElementOrder` and `renderExpanded` functions for safe, deduplicated ordering.

- Repository: [Jarrod Watts/claude-hud](https://github.com/jarrodwatts/claude-hud)
- Tags: internals
- Published: 2026-03-18

---

**Claude HUD validates user-defined element sequences through `validateElementOrder` in [`src/config.ts`](https://github.com/jarrodwatts/claude-hud/blob/main/src/config.ts) and applies them in `renderExpanded` within [`src/render/index.ts`](https://github.com/jarrodwatts/claude-hud/blob/main/src/render/index.ts), ensuring safe, deduplicated ordering with special handling for adjacent context and usage elements.**

The `jarrodwatts/claude-hud` repository provides a customizable heads-up display for Claude, allowing users to rearrange status-line elements via the `elementOrder` field in `~/.claude/hud/config.json`. This article examines how the codebase validates these custom sequences against known elements and applies them when rendering the expanded layout, ensuring type safety and preventing duplicate entries.

## Validating the `elementOrder` Configuration

Before any custom ordering reaches the renderer, it undergoes strict validation to prevent malformed configurations from breaking the display.

### The `validateElementOrder` Function

Located in [`src/config.ts`](https://github.com/jarrodwatts/claude-hud/blob/main/src/config.ts) (lines 53-75), the `validateElementOrder` function receives the raw value from the user's JSON configuration and enforces several constraints:

- **Array validation**: The input must be a non-empty array; otherwise, it falls back to `DEFAULT_ELEMENT_ORDER`.
- **Element filtering**: Each item must be a string present in the `KNOWN_ELEMENTS` set (which equals `DEFAULT_ELEMENT_ORDER`). Non-string or unknown values are silently discarded.
- **Deduplication**: A `Set` tracks seen elements, preserving only the first occurrence of each valid element.
- **Fallback protection**: If filtering results in an empty array, the function returns the default order.

```typescript
// src/config.ts – validateElementOrder
function validateElementOrder(value: unknown): HudElement[] {
  if (!Array.isArray(value) || value.length === 0) {
    return [...DEFAULT_ELEMENT_ORDER];
  }
  const seen = new Set<HudElement>();
  const elementOrder: HudElement[] = [];
  for (const item of value) {
    if (typeof item !== 'string' || !KNOWN_ELEMENTS.has(item as HudElement)) continue;
    const element = item as HudElement;
    if (seen.has(element)) continue;
    seen.add(element);
    elementOrder.push(element);
  }
  return elementOrder.length > 0 ? elementOrder : [...DEFAULT_ELEMENT_ORDER];
}

```

The `mergeConfig` function (line 333) invokes this validator when loading user settings, guaranteeing that `hudConfig.elementOrder` always contains a clean, deduplicated list of known elements.

## Applying the Element Order in the Expanded Layout

Once validated, the element order drives the rendering pipeline in the expanded view.

### Reading the Sanitized Configuration

In [`src/render/index.ts`](https://github.com/jarrodwatts/claude-hud/blob/main/src/render/index.ts) (line 62), the `renderExpanded` function retrieves the finalized order:

```typescript
const elementOrder = ctx.config?.elementOrder ?? DEFAULT_ELEMENT_ORDER;

```

This expression ensures that even if configuration loading fails, the renderer has a valid sequence to work with.

### Rendering with `renderExpanded`

The function iterates over `elementOrder` while maintaining a `seen` Set to skip any duplicates that might have slipped through. For each element, it calls `renderElementLine`, which delegates to specific line renderers like `renderProjectLine`, `renderIdentityLine`, or `renderUsageLine` located in `src/render/lines/*.ts`.

A special optimization occurs when **context** and **usage** appear consecutively (lines 73-92). Rather than rendering on separate lines, the code joins them side-by-side with a vertical bar separator:

```typescript
// src/render/index.ts – renderExpanded
function renderExpanded(ctx: RenderContext): Array<{ line: string; isActivity: boolean }> {
  const elementOrder = ctx.config?.elementOrder ?? DEFAULT_ELEMENT_ORDER;
  const seen = new Set<HudElement>();
  const lines: Array<{ line: string; isActivity: boolean }> = [];

  for (let index = 0; index < elementOrder.length; index += 1) {
    const element = elementOrder[index];
    if (seen.has(element)) continue;               // skip duplicates
    
    // special handling for context ↔ usage adjacency
    const nextElement = elementOrder[index + 1];
    if ((element === 'context' && nextElement === 'usage' && !seen.has('usage')) ||
        (element === 'usage' && nextElement === 'context' && !seen.has('context'))) {
      seen.add(element);
      seen.add(nextElement);
      const firstLine = renderElementLine(ctx, element);
      const secondLine = renderElementLine(ctx, nextElement);
      if (firstLine && secondLine) {
        lines.push({ line: `${firstLine} │ ${secondLine}`, isActivity: false });
      } else if (firstLine) {
        lines.push({ line: firstLine, isActivity: false });
      } else if (secondLine) {
        lines.push({ line: secondLine, isActivity: false });
      }
      continue;
    }

    seen.add(element);
    const line = renderElementLine(ctx, element);
    if (!line) continue;
    lines.push({ line, isActivity: ACTIVITY_ELEMENTS.has(element) });
  }
  return lines;
}

```

The output array contains objects with `line` text and an `isActivity` boolean flag, which the terminal wrapper uses to apply appropriate styling before printing.

## Practical Configuration Examples

### Custom Element Sequence

To reorder elements in your `~/.claude/hud/config.json`:

```json
{
  "elementOrder": ["context", "project", "usage", "todos"]
}

```

This configuration places the context indicator first, followed by project information, usage statistics, and finally the todos activity line.

### Consuming the Configuration

When the renderer loads this configuration:

```typescript
import { loadConfig } from './config.js';
import { render } from './render/index.js';

const ctx = { config: await loadConfig(), /* …other fields… */ };
render(ctx);   // renderExpanded reads ctx.config.elementOrder

```

Because "context" and "usage" are adjacent in the array, `renderExpanded` combines them into a single line: `Context ████░░░ 45% │ Usage ██░░░ 20% (1h 30m / 5h)`.

### Resulting Terminal Output

```

[Opus] │ my-project
Context ████░░░ 45% │ Usage ██░░░ 20% (1h 30m / 5h)
▸ Fix auth bug (2/5)

```

The first line displays the project element, the second shows the combined context and usage block due to their adjacency, and the third renders the todos activity line.

## Summary

- **Validation occurs in [`src/config.ts`](https://github.com/jarrodwatts/claude-hud/blob/main/src/config.ts)**: The `validateElementOrder` function filters unknown elements, removes duplicates, and falls back to defaults when necessary.
- **Application happens in [`src/render/index.ts`](https://github.com/jarrodwatts/claude-hud/blob/main/src/render/index.ts)**: The `renderExpanded` function respects the validated order while optimizing the display of adjacent context and usage elements.
- **Safety is enforced at load time**: The `mergeConfig` function ensures only sanitized arrays reach the rendering pipeline.
- **Special layout rules apply**: Consecutive context and usage elements render side-by-side regardless of their relative order in the configuration array.

## Frequently Asked Questions

### What happens if I include an unknown element in my `elementOrder` array?

The `validateElementOrder` function silently filters out any strings not present in the `KNOWN_ELEMENTS` set. Only valid elements appear in the final configuration, and if this filtering leaves the array empty, the system falls back to `DEFAULT_ELEMENT_ORDER`.

### Can I place activity elements like "todos" before static elements?

Yes. The renderer processes the `elementOrder` array sequentially without separating activity and non-activity elements. However, the `isActivity` flag attached to each line allows the terminal wrapper to apply distinct styling to activity indicators regardless of their position in the sequence.

### Why do context and usage appear on the same line even when I list them separately?

When `renderExpanded` detects that **context** and **usage** appear consecutively in the element order (in either direction), it joins them with a vertical bar separator (`│`) to conserve vertical space. This optimization occurs before the standard rendering logic and treats the pair as a single combined line entry.

### Where does the default element order originate?

The `DEFAULT_ELEMENT_ORDER` constant, referenced in [`src/config.ts`](https://github.com/jarrodwatts/claude-hud/blob/main/src/config.ts) and used as the fallback in [`src/render/index.ts`](https://github.com/jarrodwatts/claude-hud/blob/main/src/render/index.ts), defines the standard sequence. This constant likely resides in [`src/constants.ts`](https://github.com/jarrodwatts/claude-hud/blob/main/src/constants.ts) or a similar configuration file within the repository, establishing the baseline order when users do not provide custom preferences.