Skip to content

useLayout

The useLayout hook returns layout and visual environment information. These values may change dynamically during the session (e.g., on resize or theme toggle).

import { useLayout } from "@usefractal/frac/web";
function ThemedView() {
const { theme, maxHeight, safeArea } = useLayout();
return (
<div
style={{
backgroundColor: theme === "dark" ? "#1a1a1a" : "#ffffff",
maxHeight,
paddingTop: safeArea.insets.top,
}}
>
<p>Current theme: {theme}</p>
</div>
);
}
type LayoutState = {
theme: "light" | "dark";
maxHeight: number | undefined;
safeArea: SafeArea;
};

The current color theme of the host application ("light" or "dark").

The maximum height available for the view in pixels.

Safe area insets for devices with notches, rounded corners, or other display cutouts.

type SafeArea = {
insets: {
top: number;
right: number;
bottom: number;
left: number;
};
};
import { useLayout } from "@usefractal/frac/web";
function ThemedCard() {
const { theme } = useLayout();
const isDark = theme === "dark";
return (
<div
style={{
backgroundColor: isDark ? "#1a1a1a" : "#ffffff",
color: isDark ? "#ffffff" : "#000000",
padding: "16px",
borderRadius: "8px",
border: `1px solid ${isDark ? "#333" : "#e0e0e0"}`,
}}
>
<h3>Themed Card</h3>
<p>This card adapts to the current theme.</p>
</div>
);
}
import { useLayout } from "@usefractal/frac/web";
function View() {
const { theme } = useLayout();
return (
<div className={`view ${theme === "dark" ? "dark" : "light"}`}>
<header className="view-header">
<h2>My View</h2>
</header>
<main className="view-content">
<p>Content goes here</p>
</main>
</div>
);
}
.view {
padding: 16px;
border-radius: 8px;
}
.view.light {
background-color: #ffffff;
color: #1a1a1a;
}
.view.dark {
background-color: #1a1a1a;
color: #f5f5f5;
}
.view.light .view-header {
border-bottom: 1px solid #e0e0e0;
}
.view.dark .view-header {
border-bottom: 1px solid #333;
}
import { useLayout } from "@usefractal/frac/web";
import { useEffect } from "react";
const lightTheme = {
"--bg-primary": "#ffffff",
"--bg-secondary": "#f5f5f5",
"--text-primary": "#1a1a1a",
"--text-secondary": "#666666",
"--border-color": "#e0e0e0",
"--accent-color": "#0066cc",
};
const darkTheme = {
"--bg-primary": "#1a1a1a",
"--bg-secondary": "#2d2d2d",
"--text-primary": "#f5f5f5",
"--text-secondary": "#a0a0a0",
"--border-color": "#404040",
"--accent-color": "#4da6ff",
};
function ThemeProvider({ children }: { children: React.ReactNode }) {
const { theme } = useLayout();
const variables = theme === "dark" ? darkTheme : lightTheme;
useEffect(() => {
const root = document.documentElement;
Object.entries(variables).forEach(([key, value]) => {
root.style.setProperty(key, value);
});
}, [theme, variables]);
return <>{children}</>;
}
import { useLayout } from "@usefractal/frac/web";
type DataPoint = {
label: string;
value: number;
};
function BarChart({ data }: { data: DataPoint[] }) {
const { theme } = useLayout();
const isDark = theme === "dark";
const colors = {
bar: isDark ? "#4da6ff" : "#0066cc",
barHover: isDark ? "#80c1ff" : "#0052a3",
grid: isDark ? "#333" : "#e0e0e0",
text: isDark ? "#a0a0a0" : "#666666",
};
const maxValue = Math.max(...data.map((d) => d.value));
return (
<div className="bar-chart">
{data.map((item) => (
<div key={item.label} className="bar-container">
<div
className="bar"
style={{
width: `${(item.value / maxValue) * 100}%`,
backgroundColor: colors.bar,
}}
/>
<span style={{ color: colors.text }}>{item.label}</span>
</div>
))}
</div>
);
}
import { useLayout } from "@usefractal/frac/web";
function FullScreenView({ children }: { children: React.ReactNode }) {
const { safeArea, maxHeight } = useLayout();
return (
<div
style={{
maxHeight,
paddingTop: safeArea.insets.top,
paddingRight: safeArea.insets.right,
paddingBottom: safeArea.insets.bottom,
paddingLeft: safeArea.insets.left,
}}
>
{children}
</div>
);
}
import { useLayout } from "@usefractal/frac/web";
function Logo() {
const { theme } = useLayout();
return (
<img
src={theme === "dark" ? "/logo-light.svg" : "/logo-dark.svg"}
alt="Logo"
className="logo"
/>
);
}