Fluid Responsive Design - Viewport
Unlock advanced capabilities with styleframe Pro. This feature requires a Pro license to access.
Upgrade to ProOverview
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:
import { styleframe } from 'styleframe';
import { useFluidViewport } from '@styleframe/theme';
const s = styleframe();
const { fluidMinWidth, fluidMaxWidth, fluidScreen, fluidBreakpoint } = useFluidViewport(s);
export default s;
:root {
--fluid--min-width: 320;
--fluid--max-width: 1440;
--fluid--screen: 100vw;
--fluid--breakpoint: calc(...);
}
The default values provide:
minWidth:320- Covers small mobile devices (iPhone SE)maxWidth:1440- Covers standard laptops and smaller desktopsscreen:100vw- The current viewport widthbreakpoint: Calculated - Normalized progress value from 0 to 1
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 Width | Breakpoint Value | Percentage |
|---|---|---|
| 320px (min) | 0 | 0% through range |
| 880px (middle) | 0.5 | 50% through range |
| 1440px (max) | 1 | 100% 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:
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;
:root {
--fluid--min-width: 768;
--fluid--max-width: 1920;
--fluid--screen: 100vw;
--fluid--breakpoint: calc(...);
}
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:
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;
:root {
--fluid--min-width: 320;
--fluid--max-width: 1440;
--fluid--screen: 100vw;
--fluid--breakpoint: calc(...);
--font-size: calc(...);
}
.text {
font-size: var(--font-size);
}
Using Variables in Ranges
You can pass Styleframe variables instead of literal values for more flexible configurations:
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:
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:
: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:
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;
:root {
--fluid--min-width: 320;
--fluid--max-width: 1440;
--fluid--screen: 100vw;
--fluid--breakpoint: calc(...);
--spacing--xs: calc(...);
--spacing--sm: calc(...);
--spacing--md: calc(...);
--spacing--lg: calc(...);
--spacing--xl: calc(...);
--spacing: calc(...);
}
.container {
padding: var(--spacing);
gap: var(--spacing--md);
}
.section {
margin-top: var(--spacing--xl);
margin-bottom: var(--spacing--lg);
}
.hero {
padding-block: var(--spacing--3xl);
padding-inline: var(--spacing--2xl);
}
.button {
padding-block: var(--spacing--sm);
padding-inline: var(--spacing);
gap: var(--spacing--xs);
}
Fluid Container Widths
Create containers that scale fluidly between viewport sizes:
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:
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, useuseFluidFontSize()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.
calc() which has excellent browser support and performance. The calculations happen at render time, allowing smooth scaling at any viewport size without JavaScript.FAQ
useFluidClamp() generates a CSS calc() expression that interpolates between two values based on viewport width. CSS clamp() also takes a preferred value but uses viewport units directly (e.g., clamp(16px, 4vw, 20px)). useFluidClamp() provides more control and consistency across your design system by using a shared breakpoint calculation.calc() directly.useFluidViewport(). For mobile-only scaling, use minWidth: 320, maxWidth: 767. For desktop-only, use minWidth: 1024, maxWidth: 1920. Pass this custom breakpoint as the third argument to useFluidClamp().Overview
Create fluid, responsive designs that scale smoothly across all viewport sizes using mathematical precision. Based on the Utopia fluid type scale for optimal readability without breakpoints.
Fluid Typography
Create fluid typography systems that scale smoothly across viewports using mathematical modular scales and CSS clamp functions.