v1.0
Guides / Page 02·08

Responsive layouts

Create responsive dashboards that adapt to different screen sizes.

ResponsiveFlexiBoard wraps a board and picks a layout for the current viewport width. Each breakpoint keeps its own widget arrangement. The example uses one board whose configuration reads the current breakpoint. Its utility classes use the docs example styling:

Svelte Responsive columns
Press Enter to grab or resize widgets. Once grabbed, use Arrow keys to move/resize the widget, Enter to confirm the action, or Esc to cancel it.

Breakpoint: default, 2 columns

A
B

Resize the browser across 1024px: the grid switches between three and two columns. Move a widget at one width, resize, and move it at the other, and each arrangement is remembered separately.

Note

Breakpoints are independent

Each breakpoint renders its own separate Flexiboard. Moving, resizing, adding, or removing widgets only affects the currently active breakpoint; changes don't sync across breakpoints.

Shared board, breakpoint parameter

The example above uses a children fallback that receives the current breakpoint. This works well when you want the same board structure with different column counts or sizing:

<script lang="ts">
	import { ResponsiveFlexiBoard, FlexiBoard, FlexiTarget } from '@flexiboards/svelte';
</script>

<ResponsiveFlexiBoard config={{ breakpoints: { lg: 1024, md: 768 } }}>
	{#snippet children({ currentBreakpoint })}
		<FlexiBoard>
			<FlexiTarget
				key="main"
				config={{
					layout: {
						type: 'free',
						minColumns: currentBreakpoint === 'lg' ? 4 : currentBreakpoint === 'md' ? 3 : 2,
						maxColumns: currentBreakpoint === 'lg' ? 4 : currentBreakpoint === 'md' ? 3 : 2
					}
				}}
			/>
		</FlexiBoard>
	{/snippet}
</ResponsiveFlexiBoard>

Independent boards per breakpoint

For more control, give each breakpoint its own board content, so each one can use a different board structure. boardConfig below is a board configuration with a registry for the chart and stats types:

<script lang="ts">
	import { ResponsiveFlexiBoard, FlexiBoard, FlexiTarget } from '@flexiboards/svelte';
	import { boardConfig } from './board-config';
</script>

<ResponsiveFlexiBoard
	config={{
		breakpoints: { lg: 1024 },
		loadLayouts: () => ({
			lg: {
				main: [
					{ type: 'chart', x: 0, y: 0, width: 2, height: 2 },
					{ type: 'stats', x: 2, y: 0, width: 1, height: 1 }
				]
			},
			default: {
				main: [
					{ type: 'chart', x: 0, y: 0, width: 2, height: 2 },
					{ type: 'stats', x: 0, y: 2, width: 2, height: 1 }
				]
			}
		})
	}}
>
	{#snippet lg()}
		<FlexiBoard config={boardConfig}>
			<FlexiTarget key="main" config={{ layout: { type: 'free', minColumns: 3, maxColumns: 3 } }} />
		</FlexiBoard>
	{/snippet}

	{#snippet children({ currentBreakpoint })}
		<FlexiBoard config={boardConfig}>
			<FlexiTarget key="main" config={{ layout: { type: 'free', minColumns: 2, maxColumns: 2 } }} />
		</FlexiBoard>
	{/snippet}
</ResponsiveFlexiBoard>

Supported breakpoints

You can define breakpoints for these keys:

BreakpointDescription
lgLarge screens
mdMedium screens
smSmall screens
xsExtra-small screens

A default breakpoint (which uses children) always implicitly exists, and is used if no breakpoint is matched.

Breakpoints are minimum viewport widths, evaluated largest-first. The first match wins. Add the breakpoints property below to your responsive configuration:

const config = {
	breakpoints: {
		lg: 1200, // viewport >= 1200px uses lg
		md: 900, // viewport >= 900px uses md
		sm: 600 // viewport >= 600px uses sm
		// Below 600px, render children for the default breakpoint.
	}
};

You don’t need to define all breakpoints. If only lg and children are defined, lg is used for large screens and children for everything else.

Import and export

Use the responsive controller’s importLayout() and exportLayout() to read or replace the collection of breakpoint layouts. These excerpts extend your existing responsive board. Define responsiveConfig with its breakpoints and loader, and retain the board content in the marked space. Wire save to your application’s save button:

<script lang="ts">
	import { ResponsiveFlexiBoard, type ResponsiveFlexiBoardController } from '@flexiboards/svelte';

	let responsiveBoard = $state<ResponsiveFlexiBoardController>();

	function save() {
		if (!responsiveBoard) return;
		const layouts = responsiveBoard.exportLayout();
		localStorage.setItem('layouts', JSON.stringify(layouts));
	}
</script>

<ResponsiveFlexiBoard bind:controller={responsiveBoard} config={responsiveConfig}>
	<!-- ... -->
</ResponsiveFlexiBoard>

The responsive controller manages layouts for all breakpoints together.

Heads up

When using responsive dashboards, use the responsive methods

When a board is rendering in a responsive context, calling `importLayout()` or `exportLayout()` on the inner `FlexiBoard` will log a warning. Always use the `ResponsiveFlexiBoard` controller's methods instead.

Auto-persistence

For automatic saving, use loadLayouts and onLayoutsChange. These configuration excerpts keep your existing board content and registry. Stored data must contain layouts whose types exist in that registry:

<ResponsiveFlexiBoard
	config={{
		breakpoints: { lg: 1024 },
		loadLayouts: () => {
			const saved = localStorage.getItem('layouts');
			return saved ? JSON.parse(saved) : undefined;
		},
		onLayoutsChange: (layouts) => {
			localStorage.setItem('layouts', JSON.stringify(layouts));
		}
	}}
>
	<!-- ... -->
</ResponsiveFlexiBoard>

As with importing and exporting layouts, prefer these methods over the individual FlexiBoard’s methods on a responsive board.

Note

Lazy initialisation

Layouts are created on-demand. If a user never resizes their viewport to trigger a breakpoint, no layout is stored for it. The `onLayoutsChange` callback only includes breakpoints that have been visited.

Server-side rendering

The server cannot know the viewport, so it guesses a breakpoint. Set ssrBreakpoint to the one most visitors land on, and see Server-Side Rendering for handling the mismatch.