Fluid Design

Fluid Responsive Design - Viewport

Create fluid, responsive design tokens that scale smoothly between viewport sizes using auto-generated complex CSS calculations.
Pro Feature

Unlock advanced capabilities with styleframe Pro. This feature requires a Pro license to access.

Upgrade to Pro

Overview

The viewport composables provide the foundation for fluid responsive design in Styleframe. They handle the mathematical calculations that make values scale smoothly between minimum and maximum viewport widths without using media queries, creating truly fluid interfaces that adapt seamlessly to any screen size.

Why use viewport composables?

Viewport composables help you:

  • Eliminate media query clutter: Create values that scale smoothly without breakpoint-based jumps.
  • Ensure consistent scaling: All fluid values use the same viewport range for predictable behavior.
  • Simplify responsive design: Define min/max values and let the browser handle interpolation.
  • Improve maintainability: Change your viewport range once to update all fluid calculations.
  • Create harmonious scaling: Values transition smoothly in perfect proportion to the viewport size.

useFluidViewport()

The useFluidViewport() composable establishes the viewport range for all fluid calculations in your theme. It creates four essential CSS variables that serve as the foundation for fluid design tokens.

Default Viewport Range

Styleframe provides a carefully chosen default viewport range that covers most common devices:

styleframe.config.ts
import { styleframe } from 'styleframe';
import { useFluidViewport } from '@styleframe/theme';

const s = styleframe();

const { fluidMinWidth, fluidMaxWidth, fluidScreen, fluidBreakpoint } = useFluidViewport(s);

export default s;

The default values provide:

  • minWidth: 320 - Covers small mobile devices (iPhone SE)
  • maxWidth: 1440 - Covers standard laptops and smaller desktops
  • screen: 100vw - The current viewport width
  • breakpoint: Calculated - Normalized progress value from 0 to 1
Pro tip: Call useFluidViewport() once at the beginning of your theme configuration. All other fluid composables will automatically reference these viewport variables.

Understanding the Breakpoint Variable

The fluidBreakpoint variable is the key to fluid scaling. It represents the current viewport's position between minimum and maximum widths as a decimal value:

Viewport WidthBreakpoint ValuePercentage
320px (min)00% through range
880px (middle)0.550% through range
1440px (max)1100% through range

This normalized value makes it easy to interpolate between any min/max value pair.

Customizing the Viewport Range

You can customize the viewport range to match your design requirements:

styleframe.config.ts
import { styleframe } from 'styleframe';
import { useFluidViewport } from '@styleframe/theme';

const s = styleframe();

// Custom viewport range for tablet-to-desktop
const { fluidMinWidth, fluidMaxWidth, fluidScreen, fluidBreakpoint } = useFluidViewport(s, {
    minWidth: 768,   // Start scaling at tablet size
    maxWidth: 1920   // Stop scaling at large desktop
});

export default s;

useFluidClamp()

The useFluidClamp() composable creates individual fluid values that scale smoothly between minimum and maximum values. It's the low-level building block for all fluid design tokens.

Basic Fluid Values

Create a fluid value by providing minimum and maximum endpoints:

styleframe.config.ts
import { styleframe } from 'styleframe';
import { useFluidViewport, useFluidClamp } from '@styleframe/theme';

const s = styleframe();
const { variable, ref, selector } = s;

// Set up fluid viewport range
useFluidViewport(s);

// Create a fluid font size: 16px at mobile, 20px at desktop
const fluidFontSize = variable(
    'font-size',
    useFluidClamp(s, { min: 16, max: 20 })
);

selector('.text', {
    fontSize: ref(fluidFontSize)
});

export default s;

Using Variables in Ranges

You can pass Styleframe variables instead of literal values for more flexible configurations:

styleframe.config.ts
import { styleframe } from 'styleframe';
import { useFluidViewport, useFluidClamp } from '@styleframe/theme';

const s = styleframe();
const { variable, ref } = s;

useFluidViewport(s);

// Define min/max as variables
const fontSizeMin = variable('font-size--min', 16);
const fontSizeMax = variable('font-size--max', 20);

const fontSize = variable(
    'font-size',
    useFluidClamp(s, { min: ref(fontSizeMin), max: ref(fontSizeMax) })
);

export default s;

Using Viewport Variables

Once you've set up your fluid viewport range and created fluid values, you can reference them throughout your theme:

styleframe.config.ts
import { styleframe } from 'styleframe';
import { useFluidViewport, useFluidClamp } from '@styleframe/theme';

const s = styleframe();
const { variable, ref, selector } = s;

// Set up viewport range
useFluidViewport(s);

// Create fluid design tokens
const spacing = variable('spacing', useFluidClamp(s, { min: 16, max: 24 }));
const fontSize = variable('font-size', useFluidClamp(s, { min: 16, max: 18 }));
const borderRadius = variable('border-radius', useFluidClamp(s, { min: 8, max: 12 }));

// Use in selectors
selector('.card', {
    padding: ref(spacing),
    fontSize: ref(fontSize),
    borderRadius: ref(borderRadius)
});

selector('.container', {
    gap: ref(spacing),
    marginBlock: ref(spacing)
});

export default s;

Generated CSS:

styleframe/index.css
:root {
    --fluid--min-width: 320;
    --fluid--max-width: 1440;
    --fluid--screen: 100vw;
    --fluid--breakpoint: calc(...);
    --spacing: calc(...);
    --font-size: calc(...);
    --border-radius: calc(...);
}

.card {
    padding: var(--spacing);
    font-size: var(--font-size);
    border-radius: var(--border-radius);
}

.container {
    gap: var(--spacing);
    margin-block: var(--spacing);
}

Examples

Complete Fluid Spacing System

Create a comprehensive fluid spacing scale that adapts to viewport size:

styleframe.config.ts
import { styleframe } from 'styleframe';
import { useFluidViewport, useFluidClamp } from '@styleframe/theme';

const s = styleframe();
const { variable, ref, selector } = s;

useFluidViewport(s);

// Create fluid spacing scale
const {
    spacing,
    spacingXs,
    spacingSm,
    spacingMd,
    spacingLg,
    spacingXl,
} = useSpacing(s, {
    xs: useFluidClamp(s, { min: 4, max: 6 }),
    sm: useFluidClamp(s, { min: 8, max: 12 }),
    md: useFluidClamp(s, { min: 16, max: 24 }),
    lg: useFluidClamp(s, { min: 24, max: 32 }),
    xl: useFluidClamp(s, { min: 32, max: 48 }),
    default: '@md'
})

// Apply to components
selector('.container', {
    padding: ref(spacing),
    gap: ref(spacingMd)
});

selector('.section', {
    marginTop: ref(spacingXl),
    marginBottom: ref(spacingLg)
});

selector('.hero', {
    paddingBlock: ref(spacing3xl),
    paddingInline: ref(spacing2xl)
});

selector('.button', {
    paddingBlock: ref(spacingSm),
    paddingInline: ref(spacing),
    gap: ref(spacingXs)
});

export default s;

Fluid Container Widths

Create containers that scale fluidly between viewport sizes:

styleframe.config.ts
import { styleframe } from 'styleframe';
import { useFluidViewport, useFluidClamp } from '@styleframe/theme';

const s = styleframe();
const { variable, ref, selector } = s;

useFluidViewport(s);

// Fluid container max-widths
const containerSm = variable('container--sm', useFluidClamp(s, { min: 320, max: 640 }));
const containerMd = variable('container--md', useFluidClamp(s, { min: 640, max: 960 }));
const containerLg = variable('container--lg', useFluidClamp(s, { min: 960, max: 1280 }));
const containerXl = variable('container--xl', useFluidClamp(s, { min: 1280, max: 1600 }));

selector('.container-sm', {
    maxWidth: ref(containerSm),
    marginInline: 'auto',
    paddingInline: '1rem'
});

selector('.container-md', {
    maxWidth: ref(containerMd),
    marginInline: 'auto',
    paddingInline: '1.5rem'
});

selector('.container-lg', {
    maxWidth: ref(containerLg),
    marginInline: 'auto',
    paddingInline: '2rem'
});

selector('.container-xl', {
    maxWidth: ref(containerXl),
    marginInline: 'auto',
    paddingInline: '2.5rem'
});

export default s;

Fluid Border Radius Scale

Scale border radius values smoothly for cohesive visual design:

styleframe.config.ts
import { styleframe } from 'styleframe';
import { useFluidViewport, useFluidClamp } from '@styleframe/theme';

const s = styleframe();
const { variable, ref, selector } = s;

useFluidViewport(s);

// Fluid border radius scale
const {
    borderRadius,
    borderRadiusSm,
    borderRadiusMd,
    borderRadiusLg
} = useBorderRadius(s, {
    sm: useFluidClamp(s, { min: 4, max: 6 }),
    md: useFluidClamp(s, { min: 12, max: 16 }),
    lg: useFluidClamp(s, { min: 16, max: 24 }),
    default: '@md'
)

selector('.card', {
    borderRadius: ref(borderRadius)
});

selector('.button', {
    borderRadius: ref(borderRadiusSm)
});

selector('.modal', {
    borderRadius: ref(borderRadiusLg)
});

export default s;

Best Practices

  • Call useFluidViewport() once: Set up your fluid viewport range once at the beginning of your theme configuration. Multiple calls will override previous values.
  • Use meaningful value ranges: Choose min/max values that create noticeable but not jarring differences. Aim for 25-50% change between endpoints (e.g., 16px to 24px, not 16px to 16.5px).
  • Test at multiple viewport sizes: Always verify your fluid values at minimum, middle, and maximum viewport widths, plus various in-between sizes to ensure smooth transitions.
  • Match scaling ratios: Keep similar scaling ratios across related values. If font sizes grow by 1.5x, consider spacing growing by a similar ratio for visual harmony.
  • Document your viewport range: Always comment why you chose specific viewport widths. Link to analytics data or design requirements.
  • Consider performance: Fluid calculations use CSS calc() which is well-optimized, but avoid nesting too many calculations (3-4 levels deep maximum).
  • Use useFluidFontSize() for typography: For complete type systems with modular scales, use useFluidFontSize() instead of creating individual font size clamps.
  • Combine with media queries: Use fluid values for smooth scaling and media queries for layout changes. They complement each other.
  • Start conservative: Begin with subtle fluid scaling and increase ranges based on testing. It's easier to scale up than fix overly aggressive scaling.
  • Create custom breakpoints sparingly: Most projects only need one viewport range. Add custom breakpoints only when you have specific requirements for different device categories.
Good to know: Fluid values use CSS calc() which has excellent browser support and performance. The calculations happen at render time, allowing smooth scaling at any viewport size without JavaScript.

FAQ