> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metabind.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Visibility and interaction

> Modifiers that control whether a component is visible, interactive, or selectable

export const PlatformStatuses = ({statuses}) => {
  const StatusBadge = ({status, label}) => {
    const styles = {
      green: {
        backgroundColor: '#dcfce7',
        color: '#166534'
      },
      orange: {
        backgroundColor: '#fed7aa',
        color: '#9a3412'
      },
      red: {
        backgroundColor: '#fecaca',
        color: '#991b1b'
      },
      gray: {
        backgroundColor: '#f3f4f6',
        color: '#4b5563'
      }
    };
    const baseStyle = {
      display: 'inline-flex',
      alignItems: 'center',
      padding: '0.125rem 0.625rem',
      borderRadius: '9999px',
      fontSize: '0.875rem',
      fontWeight: '500'
    };
    const colorStyle = styles[status] || styles.green;
    return <span style={{
      ...baseStyle,
      ...colorStyle
    }}>
        {label || status}
      </span>;
  };
  const STATUS_CONFIG = {
    supported: {
      label: "Supported",
      color: "green"
    },
    partial: {
      label: "Partial",
      color: "orange"
    },
    "not-implemented": {
      label: "Not Implemented",
      color: "gray"
    }
  };
  const renderCard = (platform, value) => {
    if (!value) return null;
    const {status, note} = typeof value === "string" ? {
      status: value
    } : value;
    const config = STATUS_CONFIG[status];
    if (!config) return null;
    const titleMap = {
      ios: "SwiftUI",
      android: "Jetpack Compose",
      web: "Web"
    };
    return <Card key={platform} title={titleMap[platform] || platform}>
          <StatusBadge status={config.color} label={config.label} />
          {note && <div style={{
      marginTop: '0.5rem',
      fontSize: '0.875rem',
      color: '#6b7280'
    }}>
              {note}
            </div>}
      </Card>;
  };
  if (statuses == null) {
    return null;
  }
  return <Columns cols="3">
      {Object.entries(statuses).map(([platform, value]) => renderCard(platform, value))}
    </Columns>;
};

export const ComposeJS = ({code, name, height}) => {
  const encodedCode = useMemo(() => {
    if (!code) return "";
    try {
      return btoa(code);
    } catch (e) {
      console.error("Failed to encode code", e);
      return "";
    }
  }, [code]);
  if (!encodedCode) {
    return null;
  }
  return <iframe src={`https://www.metabind.ai/embed?code=${encodedCode}&name=${name ?? 'Example'}`} loading="lazy" style={{
    width: "100%",
    height: height || '350px',
    border: "1px solid #e5e7eb",
    borderRadius: "var(--rounded-2xl,1rem)",
    overflow: "hidden"
  }} title="ComposeJS Preview" />;
};

These modifiers govern whether a component is shown to the user, whether it responds to taps and clicks, and whether its text can be selected. `hidden` keeps the component in the layout but invisible; `allowsHitTesting` lets touches pass through without changing appearance; `disabled` blocks interaction and dims the visuals; `textSelection` toggles selectability of text content.

Pick by intent — visibility (`hidden`), interaction (`allowsHitTesting`, `disabled`), or selection (`textSelection`). For full transparency without removing hit testing, use [opacity](/bindjs/modifiers/opacity) instead.

## hidden

Hides the component while preserving its layout space.

```typescript theme={null}
.hidden(): Component
```

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "supported" },
web: { status: "supported" },
}}
/>

**Hiding a component**

The component becomes invisible but still occupies space in the layout.

```typescript theme={null}
VStack([
    Text("Visible"),
    Text("Invisible").hidden(),
    Text("Also visible")
])
```

**Conditional visibility**

```typescript theme={null}
const body = () => {
    const [showHint, setShowHint] = useState(false)

    return VStack([
        Button("Toggle Hint", () => setShowHint(!showHint)),
        showHint
            ? Text("Here is the hint")
            : Text("Here is the hint").hidden()
    ])
}
```

Unlike setting `.opacity(0)`, `.hidden()` also removes the component from hit testing — it will not respond to taps or other gestures. The component's layout space is still reserved. To remove a component from the layout entirely, conditionally exclude it from the view tree instead of using `.hidden()`.

## allowsHitTesting

Controls whether a component receives touch or click events.

```typescript theme={null}
.allowsHitTesting(enabled: boolean)
```

<ParamField path="enabled" type="boolean" required>
  When `false`, the component and all its descendants become transparent to touch and click events. Events pass through to views behind it.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "supported" },
web: { status: "supported" },
}}
/>

**Make an overlay pass through touches**

```typescript theme={null}
ZStack([
    Button("Tap me", () => handleTap()),
    Color("black")
        .opacity(0.3)
        .allowsHitTesting(false)
])
```

**Disable interaction on a watermark**

```typescript theme={null}
Image({ url: "watermark.png" })
    .resizable()
    .allowsHitTesting(false)
```

Unlike [disabled](#disabled), `allowsHitTesting(false)` does not dim the component visually. It only affects whether the component receives interaction events.

## disabled

Disables interaction with a component and dims its appearance.

```typescript theme={null}
.disabled(isDisabled: boolean)
```

<ParamField path="isDisabled" type="boolean" required>
  When `true`, the component and its descendants cannot receive interaction events. The component is also visually dimmed.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "supported" },
web: { status: "supported" },
}}
/>

**Disable a button**

```typescript theme={null}
Button("Submit", () => submit())
    .disabled(!isFormValid)
```

**Disable an entire form section**

```typescript theme={null}
VStack([
    TextField({ text, setText }),
    Button("Save", () => save())
])
    .disabled(isLoading)
```

`disabled(true)` both prevents interaction and applies a visual dimming effect. To prevent interaction without the visual change, use [allowsHitTesting(false)](#allowshittesting) instead.

## textSelection

Enables or disables user text selection.

```typescript theme={null}
.textSelection(mode: "enabled" | "disabled"): Component
```

<ParamField path="mode" type="&#x22;enabled&#x22; | &#x22;disabled&#x22;" required>
  Whether text selection is enabled or disabled.
</ParamField>

<PlatformStatuses
  statuses={{
ios: { status: "supported" },
android: { status: "supported" },
web: { status: "supported" },
}}
/>

**Enable text selection**

Allow users to select and copy text content.

```typescript theme={null}
Text("You can select and copy this text.")
    .textSelection("enabled")
```

**Disable text selection**

Prevent text from being selected, useful for labels or decorative text.

```typescript theme={null}
Text("This text cannot be selected.")
    .textSelection("disabled")
```

**Selectable paragraph**

```typescript theme={null}
VStack({ alignment: "leading", spacing: 8 }, [
    Text("Article Title")
        .font("title")
        .textSelection("disabled"),
    Text("This is the body of the article. Users can select and copy this content for sharing.")
        .textSelection("enabled"),
])
```

## See also

* [opacity](/bindjs/modifiers/opacity) — control transparency without affecting layout
* [contentShape](/bindjs/modifiers/contentShape) — change the hit-testing shape
* [onTapGesture](/bindjs/modifiers/onTapGesture) — add tap handling to non-interactive components
* [accessibilityHidden](/bindjs/modifiers/accessibilityHidden) — hide from assistive technologies
