LeCodesdocs

Live validation

Validation is derived, never stored: one signal holds what was typed, and two computeds turn it into a message and a verdict. The message, its colour and the button's enabled state all bind to those — there is no manual re-check on submit.

Fork in LeCodes
// Validation is derived, never stored: one signal holds what was typed, two computeds turn it
// into a message and a verdict, and the message, its color and the button all bind to those.
const handle = signal("")
const problem = computed(() =>
    handle.value.length < 3 ? "At least 3 characters"
        : handle.value.length > 15 ? "At most 15 characters"
        : /[^a-z0-9_]/.test(handle.value) ? "Only a–z, 0–9 and _"
        : "")
const valid = computed(() => problem.value === "")

const screen = UIScreen([
    UIText("Claim your handle").style({ fontSize: 26, fontWeight: 700, color: "white" }),
    UIInput().style({
        height: 52, px: 16, mt: 22, borderRadius: 14, bgColor: "#151922", border: "1px solid #262b36",
        color: "white", fontSize: 17, placeholder: "your_handle", placeholderColor: "#4b5163",
        autocapitalize: "none", autocorrect: false, onFocused: { borderColor: "#FF4032" },
    }).onChange(value => handle.value = value),
    UIText(() => (valid.value ? `le.codes/@${handle.value}` : problem.value)).style({
        fontSize: 13, mt: 10, ml: 4,
        color: () => (valid.value ? "#22c55e" : handle.value.length ? "#f87171" : "#8b90a0"),
    }),
    UIButton([ UIText("Claim").style({ fontSize: 16, fontWeight: 700, color: "white" }) ]).style({
        height: 52, mt: 22, borderRadius: 14, justifyContent: "center",
        bgColor: () => (valid.value ? "#FF4032" : "#20242e"),
        opacity: () => (valid.value ? 1 : 0.55), onPressed: { opacity: 0.7 },
    }).onClick(() => { if (valid.value) toast(`@${handle.value} is yours`) }),
]).style({ bgColor: "#0b0d12", px: 20, pt: "max(safe-top, 40px)" })

screen.open()

Press Run to launch the floating simulator. It stays open as you browse — opening another example just swaps the app inside it.

Related docs