Documentation

@prop

Define component properties for reusable UI.

Basic Usage

// UserCard.wh
@prop val name: String
@prop val email: String

<Card class="p-16">
  <Text fontSize={20}>{name}</Text>
  <Text color="#999">{email}</Text>
</Card>

Use the component:

<UserCard name="Alice" email="alice@example.com" />

Default Values

@prop val name: String
@prop val age: Int = 18
@prop val verified: Boolean = false

<Text>{name} ({age}) {verified ? "✓" : ""}</Text>

Optional Props

@prop val title: String
@prop val subtitle: String? = null

<Column>
  <Text>{title}</Text>
  @if (subtitle != null) {
    <Text color="#999">{subtitle}</Text>
  }
</Column>

Complex Types

data class User(val id: String, val name: String)

@prop val user: User
@prop val users: List<User>
@prop val onUserClick: (User) -> Unit

<LazyColumn>
  @for (user in users) {
    <Card onClick={() => onUserClick(user)}>
      <Text>{user.name}</Text>
    </Card>
  }
</LazyColumn>

Callback Props

@prop val onClick: () -> Unit
@prop val onSubmit: (String) -> Unit

var text = ""

<Column>
  <TextField bind:value={text} />
  <Button onClick={() => onSubmit(text)} text="Submit" />
</Column>

Usage:

<MyForm
  onSubmit={value => println("Submitted: $value")}
/>

Behind the Scenes

Whitehall transpiles @prop to Composable function parameters:

@prop val name: String
@prop val age: Int = 18

// Becomes:
@Composable
fun MyComponent(name: String, age: Int = 18) {
  // ...
}

Nested Components

// ProfileCard.wh
@prop val user: User
@prop val showActions: Boolean = true

<Card class="p-16">
  <Row class="gap-12">
    <Image src={user.avatar} w={48} h={48} />
    <Column>
      <Text fontWeight="bold">{user.name}</Text>
      <Text color="#999">{user.email}</Text>
    </Column>
  </Row>

  @if (showActions) {
    <Button text="View Profile" />
  }
</Card>

// Usage
@for (user in users) {
  <ProfileCard user={user} showActions={true} />
}

Component Composition

// Button.wh
@prop val text: String
@prop val icon: String? = null
@prop val onClick: () -> Unit

<Button onClick={onClick}>
  <Row class="gap-8">
    @if (icon != null) {
      <Icon name={icon} />
    }
    <Text>{text}</Text>
  </Row>
</Button>

// IconButton.wh
@prop val icon: String
@prop val label: String
@prop val onClick: () -> Unit

<MyButton icon={icon} text={label} onClick={onClick} />

State in Components

Components can have internal state:

@prop val initialCount: Int = 0
@prop val onChange: (Int) -> Unit = {}

var count = initialCount

<Row class="gap-8">
  <Button onClick={() => {
    count--
    onChange(count)
  }} text="-" />

  <Text>{count}</Text>

  <Button onClick={() => {
    count++
    onChange(count)
  }} text="+" />
</Row>

Case Insensitive

All annotation styles work:

@prop val name: String
@Prop val name: String
@PROP val name: String

Use @prop to create reusable components. Props are compile-time safe and generate clean Kotlin code.

Complete Example

// ShowCard.wh
data class Show(val id: String, val name: String, val poster: String)

@prop val show: Show
@prop val onCardClick: (String) -> Unit

<Card
  onClick={() => onCardClick(show.id)}
  p={16}
  class="rounded-lg"
>
  <Image
    src={show.poster}
    w={120}
    h={180}
    fit="cover"
    class="rounded"
  />
  <Text
    fontSize={16}
    fontWeight="bold"
    class="mt-2"
  >
    {show.name}
  </Text>
</Card>

// Usage
val shows = [...]

<FlowRow gap={16}>
  @for (show in shows) {
    <ShowCard
      show={show}
      onCardClick={id => $navigate("/show/$id")}
    />
  }
</FlowRow>

See Also