Components

Group

Group shows rows in a rounded box with a hairline between them, like a section of a settings screen. Each JSX child becomes one row. You can add a short caption above the box and an explanation below it.

ui-group.tsx Edit in Playground Open in new tab
// Group: settings-style rows; each child becomes a row with a divider.
import { mount, signal, Text, View } from "nsh";
import { Group, Stepper } from "nsh/ui/mobile";

function Row(props: { label: string; value: string; onPress?: () => void }) {
  return (
    <View onPress={props.onPress} style={{ flexDirection: "row", justifyContent: "space-between" }}>
      <Text>{props.label}</Text>
      <Text style={{ color: "#737373" }}>{props.value}</Text>
    </View>
  );
}

function App() {
  const notifications = signal(true);
  const size = signal(16);
  return (
    <View style={{ flex: 1, backgroundColor: "#f5f5f5", padding: 16, paddingTop: 24, gap: 24 }}>
      <Group header={<Text>ACCOUNT</Text>}>
        <Row label="Name" value="Ada Lovelace" />
        <Row label="E-mail" value="ada@example.com" />
      </Group>
      <Group
        header={<Text>PREFERENCES</Text>}
        footer={<Text>Tap the first row to toggle it. Rows are the app's JSX; the group adds the box and the dividers.</Text>}
      >
        <Row
          label="Notifications"
          value={notifications() ? "On" : "Off"}
          onPress={() => notifications.set(!notifications())}
        />
        <View style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between" }}>
          <Text>{`Text size ${size()}`}</Text>
          <Stepper value={size()} min={12} max={24} onChange={(v) => size.set(v)} />
        </View>
      </Group>
    </View>
  );
}

mount(App);
import { Group } from "nsh/ui/mobile";

#Usage

import { mount, signal, Text, View } from "nsh";
import { Group } from "nsh/ui/mobile";

function App() {
  const sounds = signal(true);
  return (
    <View style={{ flex: 1, backgroundColor: "#f5f5f5", padding: 16, paddingTop: 48 }}>
      <Group
        header={<Text>NOTIFICATIONS</Text>}
        footer={<Text>Sounds play for new messages only.</Text>}
      >
        <Text>Show previews</Text>
        <View
          onPress={() => sounds.set(!sounds())}
          style={{ flexDirection: "row", justifyContent: "space-between" }}
        >
          <Text>Sounds</Text>
          <Text>{sounds() ? "On" : "Off"}</Text>
        </View>
      </Group>
    </View>
  );
}

mount(App);

#Rows

Group wraps each child in a row with 16 pt horizontal and 12 pt vertical padding and a minimum height of 44 pt, the smallest comfortable touch target. The content is centered vertically. Every row except the first has a 1 pt divider at its top.

A row is whatever you pass, so put a <View> with flexDirection: "row" in it to place a label and a value, and an onPress on that view to make the row pressable. When a child is added or removed, for example with <If> or <Each>, the group updates the rows and dividers.

<Group header={<Text>DEVICES</Text>}>
  <Each of={devices()}>
    {(device) => (
      <View style={{ flexDirection: "row", justifyContent: "space-between" }}>
        <Text>{device().name}</Text>
        <Text style={{ color: "#737373" }}>{device().status}</Text>
      </View>
    )}
  </Each>
</Group>

The header is placed 16 pt in from the left, in small (text-sm) muted text. The footer is in extra-small (text-xs) muted text. There is an 8 pt gap between the header, the box and the footer. Both are optional.

#Props

PropTypeDefaultDescription
headerJSX.ElementnoneAbove the box: a short caption.
footerJSX.ElementnoneBelow the box: an explanation.
childrenJSX.ElementnoneThe rows, one per child.
class, classList, style, testID, onLayout, refApplied to the group’s root view (the column holding the header, box and footer).

Group has no events.

#Theming

PartTheme key
Boxgroup_box.background, corner radius radius.lg
Row text colorgroup_box.foreground
Dividersborder
Header and footer textmuted.foreground

See Theming.

#Accessibility

Group adds no roles or labels. Each row’s content is read as you wrote it. If a row is pressable, give its view an accessibilityRole (for example "button") and, if needed, an accessibilityLabel. See Accessibility.

  • Card: one surface with a header and footer.
  • ListView: virtualized rows with the same inset look.
  • Stepper: a common control inside a row.