Documentation

@for

Iterate over arrays and collections to render lists.

Basic Usage

var items = ["Apple", "Banana", "Cherry"]

<Column class="gap-8">
  @for (item in items) {
    <Text>{item}</Text>
  }
</Column>

With Objects

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

var users = [
  User("1", "Alice"),
  User("2", "Bob"),
  User("3", "Charlie")
]

@for (user in users) {
  <Card class="p-16">
    <Text>{user.name}</Text>
  </Card>
}

With LazyColumn

<LazyColumn class="gap-12">
  @for (show in shows) {
    <ShowCard show={show} />
  }
</LazyColumn>

Whitehall automatically transforms @for in lazy lists to items().

Empty State

@for (item in items) {
  <Text>{item.name}</Text>
} empty {
  <Text color="#999">No items found</Text>
}

With Index

@for (item in items.withIndex()) {
  <Row class="gap-8">
    <Text>#{item.index + 1}</Text>
    <Text>{item.value.name}</Text>
  </Row>
}

Complex Expressions

Whitehall supports complex loop expressions:

// Take first 4 items
@for (genre in genres.take(4)) {
  <Chip text={genre} />
}

// Range with condition
@for (i in 0 until minOf(4, items.size)) {
  <Text>{items[i]}</Text>
}

// With step
@for (i in 0 until items.size step 2) {
  <Text>{items[i]}</Text>
}

Filtered Lists

var searchQuery = ""
var items = [...]

val filtered = items.filter {
  it.name.contains(searchQuery, ignoreCase = true)
}

@for (item in filtered) {
  <ItemCard item={item} />
} empty {
  <Text>No results for "{searchQuery}"</Text>
}

With Keys (Animations)

Add key for list animations:

<LazyColumn>
  @for (item in items) {
    <Row key={item.id} animate:flip={{ duration: 200 }}>
      <Text>{item.name}</Text>
    </Row>
  }
</LazyColumn>

LazyVerticalGrid

<LazyVerticalGrid columns={2} gap={16}>
  @for (product in products) {
    <ProductCard product={product} />
  }
</LazyVerticalGrid>

FlowRow (Wrapping)

<FlowRow gap={8}>
  @for (tag in tags) {
    <Chip text={tag} />
  }
</FlowRow>

Nested Loops

data class Category(val name: String, val items: List<String>)

var categories = [...]

@for (category in categories) {
  <Column class="gap-8">
    <Text fontSize={20} fontWeight="bold">{category.name}</Text>

    @for (item in category.items) {
      <Text>{item}</Text>
    }
  </Column>
}

With Click Handlers

@for (show in shows) {
  <Card onClick={() => $navigate("/show/${show.id}")}>
    <Image src={show.poster} />
    <Text>{show.name}</Text>
  </Card>
}

Grouped Data

val grouped = items.groupBy { it.category }

@for ((category, items) in grouped) {
  <Column class="gap-8">
    <Text fontSize={18}>{category}</Text>
    @for (item in items) {
      <Text>{item.name}</Text>
    }
  </Column>
}

Ranges

// Simple range
@for (i in 1..10) {
  <Text>Item {i}</Text>
}

// Range to list
val numbers = 1..10
@for (n in numbers) {
  <Text>{n}</Text>
}

// With step
@for (i in 0..100 step 10) {
  <Text>{i}%</Text>
}

Behind the Scenes

Whitehall transpiles @for based on context:

Regular Composables

@for (item in items) {
  <Text>{item}</Text>
}

// Becomes:
items.forEach { item ->
  Text(text = item)
}

LazyColumn/LazyRow

<LazyColumn>
  @for (item in items) {
    <Text>{item}</Text>
  }
</LazyColumn>

// Becomes:
LazyColumn {
  items(items) { item ->
    Text(text = item)
  }
}

Use LazyColumn/LazyRow for large lists. They only compose visible items, providing better performance and memory usage.

Complete Example

data class Show(
  val id: String,
  val name: String,
  val poster: String,
  val rating: Double
)

var shows: List<Show> = []
var isLoading = true

$onMount {
  launch {
    shows = $fetch("https://api.example.com/shows")
    isLoading = false
  }
}

<Column p={16}>
  @if (isLoading) {
    <CircularProgressIndicator />
  } else {
    <LazyColumn class="gap-16">
      @for (show in shows) {
        <Card
          key={show.id}
          onClick={() => $navigate("/show/${show.id}")}
          animate:flip={{ duration: 200 }}
        >
          <Row class="gap-12">
            <Image
              src={show.poster}
              w={80}
              h={120}
              fit="cover"
              class="rounded"
            />
            <Column class="gap-4">
              <Text fontSize={18} fontWeight="bold">{show.name}</Text>
              <Row class="gap-4">
                <Icon name="star" size={16} color="#f59e0b" />
                <Text>{show.rating}/10</Text>
              </Row>
            </Column>
          </Row>
        </Card>
      } empty {
        <Column align="center" p={40}>
          <Text fontSize={18} color="#999">No shows found</Text>
        </Column>
      }
    </LazyColumn>
  }
</Column>

See Also