Responsive UI

Responsive Fission screens should describe the layout rule directly instead of scattering viewport arithmetic through every component.
Use Responsive when the branch is driven by available width. Use Length when a dimension should be fixed, percentage-based, viewport-based, intrinsic, or clamped. Use ViewHandle::viewport_size() only when the rule needs product state, environment values, previous geometry, or another input that cannot be expressed as a layout query.

The default shape

Most production screens should start with a named responsive shell.
use fission::prelude::*;

const PHONE_BREAKPOINT: f32 = 760.0;
const TABLET_BREAKPOINT: f32 = 1_120.0;

pub struct DashboardScreen;

impl From<DashboardScreen> for Widget {
    fn from(_: DashboardScreen) -> Widget {
        SafeArea {
            id: Some(WidgetId::explicit("dashboard.safe_area")),
            child: Responsive::new(DashboardDesktop)
                .id(WidgetId::explicit("dashboard.responsive"))
                .case(ResponsiveCase::max_width(PHONE_BREAKPOINT, DashboardPhone))
                .case(ResponsiveCase::between(
                    PHONE_BREAKPOINT,
                    TABLET_BREAKPOINT,
                    DashboardTablet,
                ))
                .into(),
        }
        .into()
    }
}
The fallback is the desktop layout because it is the broadest version of the screen. Ordered cases replace it when the active query width matches the breakpoint.

Declarative and imperative responsive choices

{"id":"fission-tabs-1","tabs":[{"index":0,"label":"Declarative","value":"declarative"},{"index":1,"label":"Imperative","value":"imperative"}]}
{"id":"fission-tabs-1","index":0,"label":"Declarative","value":"declarative"}
const REPORT_LIST_MIN_WIDTH: f32 = 280.0;
const REPORT_LIST_MAX_WIDTH: f32 = 420.0;

Container::new(ReportList)
    .width_length(Length::clamp(
        Length::points(REPORT_LIST_MIN_WIDTH),
        Length::percent(34.0),
        Length::points(REPORT_LIST_MAX_WIDTH),
    ))
    .flex_shrink(0.0)
    .into()
Use this when the sizing rule is independent of app state. The runtime can inspect and lower the rule consistently across native, web, static site, SSR, and terminal targets.
{"id":"fission-tabs-1","index":1,"label":"Imperative","value":"imperative"}
const INSPECTOR_BREAKPOINT: f32 = 1_120.0;

let (_ctx, view) = fission::build::current::<ReportsState>();
let viewport = view.viewport_size();
let inspector_open = view.state().inspector_open;

let content: Widget = if viewport.width >= INSPECTOR_BREAKPOINT && inspector_open {
    ReportsWithInspector.into()
} else {
    ReportsSingleColumn.into()
};
Use this when the breakpoint is not the only decision. This remains valid Fission code, but the branch is ordinary Rust and cannot be lowered as a declarative responsive case.
{"id":"fission-tabs-1"}

Container queries

Use a container query when a reusable widget should adapt to its parent instead of the whole viewport.
const WIDE_CARD_BREAKPOINT: f32 = 520.0;

Responsive::new(CompactCard)
    .container_query()
    .case(ResponsiveCase::min_width(WIDE_CARD_BREAKPOINT, WideCard))
This is useful for cards, inspectors, embedded panels, and generated app surfaces that may be mounted inside another host.

Production checklist

Question
Recommended answer
Is the branch only width-based?
Use Responsive.
Is the width clamped or proportional?
Use Length::clamp, Length::percent, Length::vw, or Length::vh.
Is the branch driven by app state too?
Use ordinary Rust in From<Component> for Widget.
Is this a reusable child component?
Prefer .container_query().
Does the layout need to be tested?
Add LiveTest resize scenarios and screenshots.
Fission
A cross-platform, GPU-accelerated user interface framework for Rust. MIT licensed.
Copyright (c) 2026 Fission
Ready to use today. Widget APIs are expected to remain stable; some runtime and shell APIs may change before 1.0.0.
Fission 0.9.2