Logic Puzzles

Kelvin Grid (beta)

Embed a fully native Wordle-style word-guessing puzzle into your mobile app with a single line of code.

KelvinGridView(model: .example)
Kelvin Grid
Kelvin Grid
Kelvin Grid

What's included

Quick integration

Embed a fully native word-guessing puzzle into your mobile app with a single line of code.

Customise grid & keyboard

Swap in your own grid cell and keyboard key views with a simple modifier on each.

Row & completion callbacks

React to every submitted guess and get notified when the game ends — win or lose.


The model

A KelvinGridModel holds the target word and the maximum number of allowed guesses. Pass in any word and attempt limit, or use .example to get started immediately.

struct KelvinGridModel {
    let word: String    // the target word to guess
    let attempts: Int   // maximum number of guesses allowed

    static var example: KelvinGridModel { get }
}

// Create a model with your own word and attempt limit
let model = KelvinGridModel(word: "SWIFT", attempts: 6)

Quick integration

Pass a puzzle model into the view to render a fully playable word-guessing grid — complete with a QWERTY keyboard — in a single line.

KelvinGridView(model: .example)

Customise grid cells

Control the spacing between cells and swap in your own custom cell view.

KelvinGridView(model: .example)
    .grid(spacing: 8, cell: KelvinGridCell.self)

Your custom cell conforms to a protocol that requires the letter to display, the evaluation state — correct, misplaced, wrong with an alphabetical offset, or empty — and whether the cell belongs to the row currently being typed.

struct KelvinGridCell: View, KelvinGridCellProtocol {
    var letter: String
    var state: KelvinCellState
    var isActiveRow: Bool

    init(letter: String, state: KelvinCellState, isActiveRow: Bool) {
        self.letter = letter
        self.state = state
        self.isActiveRow = isActiveRow
    }

    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 6)
                .fill(backgroundColor)
            if case .wrong(let offset) = state {
                VStack(spacing: 1) {
                    Text(letter).font(.title2.bold()).foregroundStyle(.white)
                    Text(offset >= 0 ? "+\(offset)" : "\(offset)")
                        .font(.system(size: 9, weight: .semibold))
                        .foregroundStyle(.white.opacity(0.85))
                }
            } else {
                Text(letter).font(.title2.bold()).foregroundStyle(foregroundColor)
            }
        }
    }
}

Customise keyboard keys

Replace the default QWERTY key buttons with your own custom key view.

KelvinGridView(model: .example)
    .grid(spacing: 8, cell: KelvinGridCell.self)
    .input(cell: KelvinKey.self)

Your custom key conforms to a protocol that requires the key label — a single letter or the delete symbol — and a tap handler to call when the key is pressed.

struct KelvinKey: View, KelvinKeyProtocol {
    var label: String
    var onTap: () -> Void

    init(label: String, onTap: @escaping () -> Void) {
        self.label = label
        self.onTap = onTap
    }

    var body: some View {
        Button(action: onTap) {
            Text(label)
                .font(.subheadline.bold())
                .frame(maxWidth: .infinity)
                .frame(height: 44)
                .background(Color(.systemGray5))
                .foregroundStyle(.primary)
                .clipShape(RoundedRectangle(cornerRadius: 6))
        }
    }
}

Row callbacks

React to every submitted row — receive the guessed word and the evaluation state for each letter position.

KelvinGridView(model: .example)
    .grid(spacing: 8, cell: KelvinGridCell.self)
    .input(cell: KelvinKey.self)
    .onInput { guess, states in
        print("Guessed: \(guess)")
    }

Completion callbacks

Get notified when the game ends — whether the player guessed the word correctly or used all their attempts.

KelvinGridView(model: .example)
    .grid(spacing: 8, cell: KelvinGridCell.self)
    .input(cell: KelvinKey.self)
    .onInput { guess, states in
        print("Guessed: \(guess)")
    }
    .onCompletion { didWin in
        print(didWin ? "Correct!" : "Game over.")
    }

Explore the examples

Focus

Explore a fully working iOS and Android sample project you can run immediately.