---
title: "JSX"
description: "Hem's checked JSX expressions, attribute shorthand, optional prop omission, and TypeScript interop boundary."
image: "https://hem.run/social/hem.png"
---

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

# JSX

Hem parses JSX directly and emits standard `.tsx`. React (or the configured JSX runtime) handles element and prop type checking, using `jsxImportSource` from the project's `tsconfig`. Hem provides specific expression and attribute syntax within standard JSX.

## Expressions in JSX holes

### Conditional `if`

Because `if` is an expression in Hem, conditional elements evaluate to `?Element`. If the condition is false, the expression renders nothing. Conditions must evaluate to a `boolean`; Hem does not support truthiness coercion.

```hem
let Notice = ({ count }: { count: number }) =>
  <section>
{if count > 0 { <p>{count} new messages</p> }}
  </section>
```

This avoids issues with the JavaScript idiom `{count && <p>…</p>}`, which renders `0` when `count` is zero.

### `match` in braces

Any Hem expression can be placed inside JSX braces, including exhaustive `match` expressions. Pattern matching is checked by the compiler before passing elements to React.

```hem
let Badge = ({ state }: { state: PaymentState }) =>
  <span className={match state {
| Paid(_) => "green"
| Refunded(_) => "red"
| Pending => "gray"
  }}>
{label(state)}
{match state {
  | Pending => <span>Waiting</span>
  | Paid(_) => <span>Paid</span>
  | Refunded(_) => <span>Refunded</span>
}}
  </span>
```

The `match` expression uses standard Hem pattern matching rules, including exhaustive coverage checking for imported discriminated unions and supported structural records.

## JSX attributes

### Prop puns

An attribute written without a value passes the in-scope binding with the matching name:

```hem
<UserCard user compact subtitle?=bio onSelect=select(user.id) />
```

This shorthand compiles to standard TSX attributes with expressions:

```tsx
<UserCard
  user={user}
  compact={compact}
  {...(bio === null || bio === undefined ? {} : { subtitle: bio })}
  onSelect={select(user.id)}
/>
```

Literals and postfix property or call expressions can omit braces (`title=name`, `count=items.length`, `onSelect=select(user.id)`). Expressions with operators must remain inside braces to avoid parsing ambiguity with JSX closing tokens (`>` and `/`).

### Optional prop omission

`attr?=value` accepts a `?T` value and omits the property from the emitted props object when absent. The shorthand `attr?` expands to `attr?=attr`.

Hem emits a conditional spread instead of `attr={undefined}`. This preserves compatibility with TypeScript's `exactOptionalPropertyTypes` and ensures React default props still apply. This rule changes only the emitted object structure; it does not make a required prop optional in component types.

### Explicit booleans

Because bare attribute names resolve to in-scope bindings, `<input checked />` passes the local `checked` variable rather than the boolean literal `true`. Write boolean literals explicitly:

```hem
let checked = isSelected(item)
<input type="checkbox" checked={checked} disabled={true} />
```

If an attribute has no matching in-scope binding, Hem reports a compile error. Write `disabled={true}` when passing literal boolean values.

## Interop boundary

JSX structure and child nodes compile directly to TSX. Text nodes remain text, and expression containers use `{}`. Hem does not modify HTML tags, JSX runtime calls, component names, or prop definitions. The emitted `.tsx` file is checked directly by TypeScript against component declarations.

Hem components can import and export React components, third-party prop types, HTML elements, refs, and JSX runtime configurations. Use TypeScript for framework-specific JSX patterns not supported in Hem, while writing application logic in Hem.

## Related diagnostics

| Code | Meaning |
| --- | --- |
| HEM2002 | A conditional expression is not boolean. |
| HEM2005 | A `match` in a JSX hole is missing coverage or has an unreachable arm. |
| HEM3003 | A JSX expression or attribute form is outside Hem's supported lowering subset. |
| HEM2016 | A JSX match pattern has an ambiguous binding. |

## Normative source

Specification 0.2, §6–§7 (expressions and control flow), §11 (JSX), and grammar
0.2 Appendix A.11. The tour's JSX overview is section 12.

Source: https://hem.run/reference/jsx/index.mdx
