Skip to content

Guide

Controls and inputs

Making a box interactive, and how auto-growing fields behave.

Any box can become a control

Mark a shape interactive and choose what it behaves as: a button or a text input. The box keeps its fill, border, and corner radius from the shape settings, and gains behavior on top.

This is deliberately not a separate widget library. A control is a box you drew, which means a button looks exactly like the rectangle you designed, not like a framework's idea of a button.

Buttons

A button does one of two things on press:

  • Navigate to another screen in the same project. Pick the target screen and the generated code wires the transition.
  • Call a handler you name. The generated code emits a call to that function name, which you implement in your own codebase.

The second is the common case when you are dropping a screen into a real app: name the handler what your code already calls it, and the export lines up.

Text inputs

An input carries the properties you would expect to have to set by hand on four platforms:

  • Placeholder text.
  • Secure entry for passwords, which obscures the typed value.
  • Keyboard type: default, email, number, phone, or URL. Each generator maps this to its platform's own keyboard type.
  • Text colour, separate from the box fill, so the typed text and the background are independent.
  • Multiline, which is where it gets interesting.

Multiline fields and push-down

A single-line field is straightforward. A multiline field is the hardest behavior in the product to get right, and it is worth understanding what happens.

On the canvas, a multiline field grows as text wraps, and everything below it moves down. Matching that in generated code means each platform has to measure its own text, in its own font, at its own width, and then shift subsequent elements by the measured amount. There is no shared abstraction to lean on; each generator solves it natively:

  • React Native measures via onContentSizeChange.
  • SwiftUI uses a TextEditor that measures its own wrapped text.
  • Flutter measures the TextField's height against its own font, size, and width.
  • HTML uses the browser's scrollHeight and shifts following elements with a small script.

All four also extend the scrollable content height as the field grows, so a form that outgrows the device still scrolls to its true bottom.

If you are evaluating whether generated code actually behaves like the design rather than merely resembling it, this is the behavior to test. It is where approximation shows up fastest.

Here is the push-down in isolation: a multiline field with a plain box sitting below it, React Native output. ih1 is the field's live measured height, seeded at its designed height (80) and grown by onContentSizeChange. The box below reads top: 150 + (Math.max(0, ih1 - 80)), its designed position plus however far the field has grown past its floor. Nothing else on the page needs to know the field exists.

import React from 'react';
import { View, TextInput, ScrollView, StyleSheet } from 'react-native';

export default function PushDown() {
  const [ih1, setIh1] = React.useState(80);

  return (
    <ScrollView style={styles.screen} contentContainerStyle={{ minHeight: 300 + (Math.max(0, ih1 - 80)) }} showsVerticalScrollIndicator={false}>
      <TextInput style={[styles.note, { height: ih1 }]} placeholder={"Add a note..."} placeholderTextColor="#9ca3af" multiline scrollEnabled={false} onContentSizeChange={(e) => setIh1(Math.max(80, e.nativeEvent.contentSize.height + 2))} />
      <View style={[styles.below, { top: 150 + (Math.max(0, ih1 - 80)) }]} />
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  screen: {
    flex: 1,
    backgroundColor: '#ffffff',
  },
  note: {
    position: 'absolute',
    left: 20,
    top: 40,
    width: 260,
    minHeight: 80,
    backgroundColor: '#ffffff',
    color: '#17171A',
    fontFamily: 'Inter',
    fontSize: 16,
    paddingHorizontal: 12,
    paddingVertical: 10,
    textAlignVertical: 'top',
    overflow: 'hidden',
    textAlign: 'left',
    borderRadius: 12,
    borderWidth: 1,
    borderColor: '#E4E1D8',
  },
  below: {
    position: 'absolute',
    left: 20,
    top: 150,
    width: 260,
    height: 52,
    borderRadius: 12,
    backgroundColor: '#F6F4EE',
    borderWidth: 1,
    borderColor: '#E4E1D8',
  },
});

What controls do not do

  • No validation, no form state, no submission handling. You get the field and its behavior; the logic is yours.
  • No focus ordering or keyboard navigation beyond platform defaults.
  • No custom control types beyond buttons and inputs today.