UI & Layout
Build interfaces with views, text, and flex
Every built-in widget ships as an uppercase wrapper component imported from solid-fuse. They take typed props and render to native Flutter widgets.
View
View is the workhorse layout container. It maps to a composition of Flutter widgets — Container, Padding, SizedBox, Opacity, Transform, and more — depending on which props you set.
import { Text, View } from "solid-fuse";
<View
padding={16}
decoration={{ color: "white", borderRadius: 12, shadow: [{ blurRadius: 8, color: "#0002" }] }}
>
<Text fontSize={18}>Card content</Text>
</View>View props
| Prop | Type | Description |
|---|---|---|
flex | FlexInput | Layout children (see Flex below) |
padding | EdgeInsetsInput | Inner spacing |
margin | EdgeInsetsInput | Outer spacing |
width / height | number | Fixed dimensions |
minWidth / maxWidth | number | Size constraints |
minHeight / maxHeight | number | Size constraints |
aspectRatio | number | Width-to-height ratio |
alignment | AlignmentString | Align child within view |
decoration | DecorationInput | Background, borders, shadows, gradients |
foregroundDecoration | DecorationInput | Decoration painted on top |
opacity | number | 0–1 transparency |
transform | TransformInput | Rotate, scale, translate |
clipBehavior | string | Clip behavior ("hardEdge", "antiAlias", "none") |
grow | number | Flex grow factor (when inside a flex parent) |
fit | string | "tight" or "loose" (with grow) |
visible | boolean | Show/hide without removing from tree |
ignorePointer | boolean | Disable hit testing |
Text
Text renders Flutter Text widgets.
import { Text } from "solid-fuse";
<Text fontSize={24} fontWeight="bold" color="blue">
Hello world
</Text>Text props
| Prop | Type | Description |
|---|---|---|
fontSize | number | Font size in logical pixels |
fontWeight | FontWeight | 100–900 or "thin", "light", "regular", "medium", "semiBold", "bold", "extraBold", "black" |
fontFamily | string | Font family name |
fontStyle | string | "normal" or "italic" |
color | ColorInput | Text color |
lineHeight | number | Line height multiplier |
letterSpacing | number | Space between characters |
textAlign | string | "left", "right", "center", "justify", "start", "end" |
maxLines | number | Maximum number of lines |
overflow | string | "clip", "fade", "ellipsis", "visible" |
textDecoration | string | "underline", "overline", "lineThrough", "none" |
backgroundColor | ColorInput | Highlight behind text |
shadows | ShadowInput[] | Text shadows |
Stack and Positioned
Stack layers children on top of each other. Positioned places children at specific coordinates within the stack.
import { Positioned, Stack, Text, View } from "solid-fuse";
<Stack alignment="center">
<View width={200} height={200} decoration={{ color: "blue" }} />
<Positioned top={10} right={10}>
<Text color="white">Badge</Text>
</Positioned>
</Stack>| Stack prop | Type | Description |
|---|---|---|
alignment | AlignmentString | Default alignment for children |
fit | string | "loose", "expand", "passthrough" |
clipBehavior | string | Clip overflowing children |
| Positioned prop | Type | Description |
|---|---|---|
top / left / right / bottom | number | Offset from stack edges |
width / height | number | Fixed dimensions |
ScrollView
ScrollView creates a scrollable container.
import { ScrollView, Text, View } from "solid-fuse";
<ScrollView padding={16} physics="bouncing">
<View flex={{ gap: 8 }}>
<For each={items()}>{(item) => <Text>{item}</Text>}</For>
</View>
</ScrollView>| Prop | Type | Description |
|---|---|---|
scrollDirection | string | "vertical" (default) or "horizontal" |
physics | string | "bouncing", "clamping", "always", "never", "page" |
reverse | boolean | Reverse scroll direction |
padding | EdgeInsetsInput | Inner padding |
controller | ScrollController | Programmatic scroll control (see Handles) |
clipBehavior | string | Clip behavior |
keyboardDismissBehavior | string | "manual" or "onDrag" |
Flex
Every element that accepts children supports flex — this is intentional. JSX can't enforce "exactly one child" vs "many children" at the type level, so instead of separate Row and Column components, Fuse uses flex as a universal layout config.
{/* Horizontal row with spacing */}
<View flex={{ direction: "horizontal", gap: 12 }}>
<Text>Left</Text>
<Text>Right</Text>
</View>
{/* Centered content filling available space */}
<View flex={{ align: "center", justify: "center", expand: true }}>
<Text>Centered</Text>
</View>
{/* Space between items */}
<View flex={{ justify: "spaceBetween" }}>
<Text>Start</Text>
<Text>End</Text>
</View>FlexInput
| Property | Type | Default | Description |
|---|---|---|---|
direction | string | "vertical" | "horizontal" or "vertical" |
gap | number | 0 | Spacing between children (pixels) |
align | string | "start" | Cross-axis: "start", "center", "end", "stretch" |
justify | string | "start" | Main-axis: "start", "center", "end", "spaceBetween", "spaceAround", "spaceEvenly" |
expand | boolean | false | Fill available space on main axis |
Grow
Use grow on children to distribute extra space, similar to CSS flex-grow:
<View flex={{ direction: "horizontal", expand: true }}>
<View grow={1} decoration={{ color: "red" }}>
<Text>Takes remaining space</Text>
</View>
<View width={100} decoration={{ color: "blue" }}>
<Text>Fixed 100px</Text>
</View>
</View>GestureDetector
GestureDetector wraps content and handles touch, mouse, and trackpad events. It maps to Flutter's GestureDetector.
import { GestureDetector, Text, View } from "solid-fuse";
<GestureDetector onTap={() => setCount((c) => c + 1)}>
<View padding={16} decoration={{ color: "blue", borderRadius: 8 }}>
<Text color="white">Tap me</Text>
</View>
</GestureDetector>All standard Flutter gesture callbacks are available: onTap, onTapDown, onTapUp, onDoubleTap, onLongPress, onVerticalDragUpdate, onHorizontalDragUpdate, onPanUpdate, onScaleUpdate, and their variants. TypeScript autocomplete will show the full list.
Event data
Events include both global and local positions:
<GestureDetector
onTapDown={(e) => console.log(e.localX, e.localY)}
onPanUpdate={(e) => {
// e.dx, e.dy — delta since last update
setX((x) => x + e.dx);
setY((y) => y + e.dy);
}}
onPanEnd={(e) => {
// e.vx, e.vy — velocity in pixels per second
console.log("flung at", e.vx, e.vy);
}}
>
{/* ... */}
</GestureDetector>| Field | Present on | Description |
|---|---|---|
x, y | All position events | Global position |
localX, localY | All position events | Position relative to the widget |
dx, dy | Drag/pan/scale update | Delta since last update |
vx, vy | End events | Velocity (pixels/sec) |
scale, rotation | Scale update | Pinch scale factor and rotation (radians) |
kind | Start/down events | "touch", "mouse", "stylus", "trackpad" |
Hit test behavior
The behavior prop controls how the detector responds to touches:
| Value | Description |
|---|---|
"deferToChild" | Default. Only the child's area is tappable. |
"opaque" | Entire bounds tappable, blocks gestures behind. |
"translucent" | Entire bounds tappable, gestures pass through. |
GestureDetector also supports flex for layout, so you can use it as a tappable container directly.
Input types
These types are used across multiple elements for colors, spacing, borders, and more.
ColorInput
Colors can be specified as named strings, hex codes, or RGB/HSL objects:
// Named
<Text color="red" />
<Text color="transparent" />
// Hex (3, 4, 6, or 8 character) — CSS semantics, alpha LAST (#RGBA / #RRGGBBAA)
<View decoration={{ color: "#F00" }} />
<View decoration={{ color: "#FF0000" }} />
<View decoration={{ color: "#FF000080" }} /> {/* red at 50% alpha */}
// RGB object (r/g/b: 0-255, a: 0-1)
<View decoration={{ color: { r: 255, g: 0, b: 0 } }} />
<View decoration={{ color: { r: 255, g: 0, b: 0, a: 0.5 } }} />
// HSL object (h: 0-360, s/l: 0-100, a: 0-1)
<View decoration={{ color: { h: 0, s: 100, l: 50 } }} />EdgeInsetsInput
Padding and margin accept a number (all sides) or an object:
<View padding={16} /> {/* all sides */}
<View padding={{ horizontal: 16, vertical: 8 }} /> {/* symmetric */}
<View padding={{ top: 10, left: 20, bottom: 10 }} /> {/* individual */}BorderRadiusInput
<View decoration={{ borderRadius: 12 }} /> {/* all corners */}
<View decoration={{ borderRadius: { topLeft: 12, topRight: 12 } }} /> {/* individual */}DecorationInput
Combines background, borders, shadows, gradients, and images:
<View decoration={{
color: "white",
borderRadius: 12,
border: { width: 1, color: "#E0E0E0" },
shadow: [{ blurRadius: 8, spreadRadius: 1, color: "#0001", offsetY: 2 }],
gradient: {
type: "linear",
colors: ["blue", "purple"],
begin: "topLeft",
end: "bottomRight",
},
}} />TransformInput
<View transform={{ rotate: 0.1 }} /> {/* radians */}
<View transform={{ scale: 1.5 }} />
<View transform={{ translateX: 10, translateY: -5 }} />