Open-source project
airbnb/epoxy-ios avatar
airbnb/epoxy-ios

Epoxy gives UIKit a declarative API, one podspec at a time

Epoxy is a suite of declarative UI APIs for building UIKit applications in Swift

1,317 stars79 forksSwiftApache-2.0

At a glance

What is it?
Epoxy is Airbnb's suite of declarative APIs for building UIKit screens in Swift, covering collection views, navigation stacks, modal presentations, bars and composable layouts in separate modules. It is not a rendering layer: it drives the UIKit classes you already use, with the identity and styling model you choose.
Who is it for?
Adopt Epoxy if you maintain a large UIKit codebase and want one declarative style across collection views, navigation and modals rather than a mix of approaches. Compare it with SwiftUI before choosing it for a new app.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

It drives UIKit rather than replacing it

Epoxy is described as a suite of declarative UI APIs for building UIKit applications in Swift, and the inspiration is named twice: the Epoxy framework on Android from the same company, and SwiftUI.

The scope claim is concrete. Epoxy was developed at Airbnb and powers thousands of screens in apps shipped to millions of users, refined over years by dozens of contributors. The README then shows four sample screens from the Airbnb app, home details, home photos, messaging and registration, and makes the point that usages range from simple static forms to their most advanced dynamic features.

The important qualifier is what Epoxy is not. Each module is described as a declarative API for driving a specific UIKit class: a `UICollectionView`, a `UINavigationController`, the modal presentations of a `UIViewController`, the bar stacks of a `UIViewController`. So the views that end up on screen are UIKit views, laid out and configured through a declarative layer.

That has a practical consequence. Your existing `UICollectionView` knowledge, cell reuse behaviour and layout attributes still apply. Epoxy changes how you describe the content, not what class draws it, which is why it remains viable in a large UIKit codebase that cannot be rewritten.

Six modules, and the umbrella product is the exception

The module table is the clearest statement of scope in the README, and its first line is a warning in disguise.

`Epoxy` includes all of the below modules in a single import statement. Everything else is separate.

`EpoxyCollectionView` is a declarative API for driving the content of a `UICollectionView`. `EpoxyNavigationController` does the same for the navigation stack of a `UINavigationController`. `EpoxyPresentations` covers the modal presentations of a `UIViewController`, and `EpoxyBars` handles fixed top and bottom bar stacks on a view controller.

`EpoxyLayoutGroups` is the module with the most distinct job. It is for building composable layouts in UIKit with a syntax similar to SwiftUI's stack APIs, so a vertical or horizontal arrangement is declared rather than computed inside a layout subclass.

`EpoxyCore` is the foundation. It holds the foundational APIs used to build all of the Epoxy declarative UI APIs, and its documentation lives in the project wiki rather than in this table.

The architecture is stated as modular so you include only what your use case needs, and that is enforced by the packaging rather than by documentation alone. Under CocoaPods each module has its own podspec, and under Swift Package Manager each module is a separate library product.

For a team integrating Epoxy into an existing app, that matters more than it sounds. Taking only `EpoxyCore` and `EpoxyCollectionView` is a small addition; taking the umbrella product includes all six modules at once.

Two install routes, one dependency URL

Epoxy can be installed with CocoaPods or Swift Package Manager, and the two paths differ in how finely you can select.

With CocoaPods the podspec line is:

ruby
pod 'Epoxy'

and the README points at the CocoaPods integration instructions afterwards. The important sentence is the one about podspecs: Epoxy is separated into podspecs for each module so you only have to include what you need. The repository root confirms this with separate `Epoxy.podspec`, `EpoxyBars.podspec`, `EpoxyCollectionView.podspec`, `EpoxyCore.podspec`, `EpoxyLayoutGroups.podspec`, `EpoxyNavigationController.podspec` and `EpoxyPresentations.podspec` files, plus a `ConfigurePodspec.rb` that presumably generates them.

With Swift Package Manager the instructions are Xcode's own: File, then Swift Packages, then Add Package Dependency, and enter `https://github.com/airbnb/epoxy-ios.git` at the current version. Same idea here, with the word being library products rather than podspecs.

There is a compatibility badge for SPM, and the platform and Swift version badges are generated from the Swift Package Index, so the supported deployment targets are served rather than written in prose.

For documentation there are three sources rather than one. The wiki holds the full documentation and step-by-step tutorials, the Epoxy DocC documentation on the Swift Package Index provides type-level documentation, and the Example directory holds a full sample app with many examples that you can either run through the `EpoxyExample` scheme in `Epoxy.xcworkspace` or read the source of.

A collection view is constructed from an items closure

The getting started example is short enough to read as the whole mental model.

`EpoxyCollectionView` provides the declarative API, and `CollectionViewController` is a subclassable `UIViewController` that spins up a `UICollectionView`-backed view controller for you. You can instantiate it directly with sections and items:

swift
let viewController = CollectionViewController(
  layout: UICollectionViewCompositionalLayout
    .list(using: .init(appearance: .plain)),
  items: {
    TextRow.itemModel(
      dataID: DataID.row,
      content: .init(title: "Tap me!"),
      style: .small)
      .didSelect { _ in
        // Handle selection
      }
  })

Several ideas in this example. The layout is a compositional layout configured with the list appearance, which is UIKit's own configuration rather than a custom layout class. The content is an items closure rather than a data source object. The row is built from an itemModel carrying a dataID, its content and a style. And selection is attached as a chained `didSelect` on the model rather than implemented by conforming to a delegate protocol.

The example uses `TextRow`, described as a simple `UIView` containing two labels that conforms to the `EpoxyableView` protocol. That protocol is the seam: your view conforms, Epoxy supplies the styling and behaviour, and the same declaration pattern applies to any view that conforms.

The README also shows the subclass route, where you make your own subclass of `CollectionViewController` instead of instantiating it inline.

Cadence, tooling and what is in the repository

The release history is slow and regular, which fits a library tied to a platform rather than to a language.

0.9.0 was released on 2022-10-25, 0.10.0 on 2023-07-05 and 0.11.0 on 2025-06-18. That is roughly one release per year to year and a half, and the version numbers are 0.x rather than 1.0, which for an Airbnb-internal-origin library usually means the public API is not promised to be stable.

The last push was on 2026-09-30, so commits continue between releases. The project is still on the `master` branch rather than `main`.

The repository is dual-packaged, and that shows in the tooling. Alongside the podspecs there is a `Package.swift` and a `Package.resolved`, and there is a `Gemfile` with a `Gemfile.lock` for the CocoaPods side plus a `Rakefile`. A `CHANGELOG.md`, `CONTRIBUTING.md` and `CODE_OF_CONDUCT.md` complete the usual set.

The `Sources/` directory holds the module code, `Tests/` the tests, `docs/` the documentation assets and logo, and `Example/` the sample application with its own `EpoxyExample.xcodeproj/`.

The FAQ and getting started sections in the README are truncated in what is published here, so the wiki is where the rest lives, including the EpoxyCore page that the module table links to.

The licence is Apache-2.0, which is permissive for commercial use and includes an explicit patent grant.

Editorial conclusion

Adopt Epoxy if you maintain a large UIKit codebase and want one declarative style across collection views, navigation and modals rather than a mix of approaches. Compare it with SwiftUI before choosing it for a new app. Verify first which module you actually need and install only that one, since the umbrella `Epoxy` product pulls in all of them.

Frequently asked questions

How do I install epoxy-ios?

With CocoaPods, add `pod 'Epoxy'` to your Podfile, or add `https://github.com/airbnb/epoxy-ios.git` through Xcode's Swift Packages menu. Each module is packaged separately in both cases, so you can depend on one module rather than the umbrella product.

What modules does Epoxy contain?

There are seven: the umbrella `Epoxy` product that imports everything below it, plus `EpoxyCore` for foundational APIs, `EpoxyCollectionView`, `EpoxyNavigationController`, `EpoxyPresentations`, `EpoxyBars`, and `EpoxyLayoutGroups` for SwiftUI-style stack layouts.

Where is the documentation for Epoxy?

Full documentation and tutorials are in the project wiki, type-level documentation is in the Epoxy DocC documentation hosted on the Swift Package Index, and the `Example` directory contains a sample app runnable through the `EpoxyExample` scheme in `Epoxy.xcworkspace`.

Official sources

  1. airbnb/epoxy-ios on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/airbnb-epoxy-ios.svg)](https://hysenlabs.com/projects/airbnb-epoxy-ios)

Was this useful?