Size System

Learn how AudioUI's size system works, from base units to multipliers, aspect ratios, and fixed vs adaptive sizing.

AudioUI uses a base unit system for consistent component sizing across all default components. This system ensures mathematical harmony between components and provides both fixed and adaptive sizing options.

Overview

The size system provides:

  • Consistent sizing: All components derive from a single base unit
  • Mathematical harmony: Components align perfectly in layouts
  • Flexible sizing: Choose between fixed sizes or adaptive container-filling
  • Themeable: Scale the entire system by changing the base unit

Base Unit System

All component sizes derive from a single base unit:

  • Base Unit: --audioui-unit (default: 48px)
  • Size Multipliers:
    • xsmall: 1x base unit (48px)
    • small: 1.25x base unit (60px)
    • normal: 1.5x base unit (72px) - default
    • large: 2x base unit (96px)
    • xlarge: 2.5x base unit (120px)

CSS Variable Structure

Size dimensions are defined using CSS variables:

/* Base unit */
--audioui-unit: 48px;
 
/* Size multipliers */
--audioui-size-mult-xsmall: 1;
--audioui-size-mult-small: 1.25;
--audioui-size-mult-normal: 1.5;
--audioui-size-mult-large: 2;
--audioui-size-mult-xlarge: 2.5;
 
/* Square components (Button, Knob, CycleButton) */
--audioui-size-square-{size}: calc(var(--audioui-unit) * var(--audioui-size-mult-{size}));
 
/* Horizontal Slider (1x2) */
--audioui-size-hslider-height-{size}: calc(var(--audioui-unit) * multiplier);
--audioui-size-hslider-width-{size}: calc(height * 2);
 
/* Vertical Slider (2x1) */
--audioui-size-vslider-width-{size}: calc(var(--audioui-unit) * multiplier);
--audioui-size-vslider-height-{size}: calc(width * 2);
 
/* Keys (1x5) */
--audioui-size-keys-height-{size}: calc(var(--audioui-unit) * multiplier);
--audioui-size-keys-width-{size}: calc(height * 5);

Component Aspect Ratios

Each component type has a fixed aspect ratio that defines its shape:

  • Button, Knob, CycleButton: 1x1 (square)
  • Horizontal Slider: 1x2 (width:height) - width > height
  • Vertical Slider: 2x1 (width:height) - height > width
  • Keys: 1x5 (width:height) - width > height

These aspect ratios are preserved at all sizes, ensuring components maintain their visual identity.

Fixed Sizing

When adaptiveSize={false} (default), components use fixed sizes based on the size prop:

FixedSizing.tsx
import { Knob, Slider } from "@cutoff/audio-ui-react";
 
export default function FixedSizingExample() {
  return (
    <div style={{ display: "flex", gap: "20px", alignItems: "flex-end" }}>
      <Knob value={0.5} onChange={(e) => {}} label="Small" size="small" />
      <Knob value={0.5} onChange={(e) => {}} label="Normal" size="normal" />
      <Knob value={0.5} onChange={(e) => {}} label="Large" size="large" />
      <Slider
        value={0.5}
        onChange={(e) => {}}
        label="Slider"
        size="normal"
        orientation="horizontal"
      />
    </div>
  );
}

How Fixed Sizing Works

  1. CSS Classes: Size class is applied for semantic purposes and external styling
  2. Inline Styles: Size dimensions are applied as inline styles (CSS variable references) to override AdaptiveBox's default 100% sizing
  3. Precedence: User className and style props take precedence over size classes/styles

Adaptive Sizing

When adaptiveSize={true}, components fill their container and ignore the size prop for layout constraints:

AdaptiveSizing.tsx
import { Knob } from "@cutoff/audio-ui-react";
 
export default function AdaptiveSizingExample() {
  return (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "1fr 1fr",
        gap: "20px",
        height: "200px",
      }}
    >
      <Knob
        value={0.5}
        onChange={(e) => {}}
        label="Adaptive"
        adaptiveSize={true}
      />
      <Knob
        value={0.5}
        onChange={(e) => {}}
        label="Adaptive"
        adaptiveSize={true}
      />
    </div>
  );
}

How Adaptive Sizing Works

  1. No size class or inline size styles are applied
  2. AdaptiveBox's default width: 100%; height: 100% takes effect
  3. Component fills its container while maintaining aspect ratio

Size Props

All controls support a size prop and an adaptiveSize prop:

type SizeType = "xsmall" | "small" | "normal" | "large" | "xlarge";
 
type AdaptiveSizeProps = {
  size?: SizeType; // default: "normal"
  adaptiveSize?: boolean; // default: false
};

Customization

Changing Base Unit

Scale the entire size system by modifying the base unit:

:root {
  --audioui-unit: 60px; /* Increase from 48px to 60px */
}

All components will scale proportionally while maintaining their aspect ratios:

  • xsmall: 60px (was 48px)
  • small: 75px (was 60px)
  • normal: 90px (was 72px)
  • large: 120px (was 96px)
  • xlarge: 150px (was 120px)

Overriding Size

Users can override size constraints using className or style props. User-provided styles are spread after size styles, so they take precedence:

SizeOverride.tsx
import { Knob } from "@cutoff/audio-ui-react";
 
export default function SizeOverrideExample() {
  return (
    <div style={{ display: "flex", gap: "20px" }}>
      {/* CSS class override (if higher specificity) */}
      <Knob
        value={0.5}
        onChange={(e) => {}}
        label="CSS Override"
        size="normal"
        className="w-20 h-20"
      />
      {/* Inline style override (always wins) */}
      <Knob
        value={0.5}
        onChange={(e) => {}}
        label="Inline Override"
        size="normal"
        style={{ width: "100px", height: "100px" }}
      />
    </div>
  );
}

Design System Consistency

The size system ensures mathematical harmony between components:

  • A "small" knob (1x1) aligns perfectly with a "small" slider's track width
  • All components use the same base unit and multipliers
  • Size variations are consistent across component types
  • Aspect ratios are preserved at all sizes

Performance Considerations

  • CSS Variables: Size classes use CSS variables (computed at render time, cached by browser)
  • No JavaScript Calculations: All sizing is CSS-based
  • Optimized Merging: Simple object spread for style merging (no useMemo overhead needed)
  • Memoization: All controls are wrapped with React.memo - style objects are only created when props change

Integration with AdaptiveBox

The size system works in conjunction with AdaptiveBox:

  • Fixed Sizing: Size dimensions override AdaptiveBox's default 100% sizing
  • Adaptive Sizing: AdaptiveBox's container-filling behavior takes effect
  • Aspect Ratios: Both systems respect component aspect ratios

Next Steps