
If you already know ConstraintLayout from the XML world, you do not have to throw that knowledge away in Jetpack Compose. Google ships a constraintlayout-compose library that gives you the same anchoring model (createRefs, constrainAs, linkTo) inside a @Composable. One honest caveat for 2026 though: in Compose, ConstraintLayout is a readability tool, not a performance tool. The flat-hierarchy speed argument that made it the default in XML does not carry over, so reach for it when a layout gets awkward to express with Row, Column, and Box, not by default.
I first wrote this post in 2021 when the Compose version of ConstraintLayout was still a release candidate. The API has held up well since then, so most of this still applies. I have refreshed the framing around when to use it and cleaned up the setup.
Why keep using ConstraintLayout in Compose?
For years, ConstraintLayout was an effective way to create complex UI for our Android applications. With ConstraintLayout you were able to build a complete screen without nesting the old-fashioned layout containers such as LinearLayout, RelativeLayout, and the rest. A flat view hierarchy was faster to measure and lay out, so it became the default for anything non-trivial.
When Google released the first stable version of Jetpack Compose, every developer suddenly had to explore, learn, and adapt to a new way of building UI. For developers like me, giving up ConstraintLayout felt like a big No-No, and relearning layout from scratch was not something I was looking forward to.
They shipped a library that lets you use ConstraintLayout inside Compose. Happy days. In the View system, ConstraintLayout earned its place partly on performance: flattening a deep hierarchy meant fewer measure and layout passes. Compose handles deep layout trees efficiently on its own, so that particular win does not transfer. When you have a handful of views with relationships like “center this to the parent, then hang that one off its bottom edge,” constraints read more cleanly than a stack of nested Box and Column modifiers.
How to add ConstraintLayout to a Compose project
- Open your module
build.gradle(orbuild.gradle.kts) where your other Compose dependencies live. - Add the
constraintlayout-composedependency. Use the current stable version (1.1.1 as of mid-2026, released Feb 2025):
implementation 'androidx.constraintlayout:constraintlayout-compose:1.1.1'
- Confirm the latest stable version on the official releases page before you sync: https://developer.android.com/jetpack/androidx/releases/constraintlayout
- Sync Gradle, then import
ConstraintLayoutfromandroidx.constraintlayout.composeinside your composable file.
That is the whole setup. This is a separate artifact from the classic androidx.constraintlayout:constraintlayout you used in the View world, so adding one does not give you the other.
How do you translate XML ConstraintLayout to Compose?
The concept behind it is identical:
“left of my view to parent left, and top of my view to bottom of another view…”
But you now express that in Kotlin, and it can feel like you are building old-fashioned views programmatically:
val textView = TextView(context)
.... code ...
addView(textView)
That comparison is only about the mental model, not the actual API.

A bare container looks like this:
// In XML
<androidx.constraintlayout.widget.ConstraintLayout>
// Code
</androidx.constraintlayout.widget.ConstraintLayout>
// In Jetpack Compose ConstraintLayout
@Composable
fun ConstraintCompose() {
ConstraintLayout {
// Code
}
}
If you want the ConstraintLayout to match the parent on width and height:
// In XML
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
// Code
</androidx.constraintlayout.widget.ConstraintLayout>
// In Jetpack Compose ConstraintLayout
@Composable
fun ConstraintCompose() {
ConstraintLayout(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
) {
// Code
}
}
How do you constrain two views to each other?
In this example we create two views and position the second one relative to the first. In XML you assign IDs and reference them from the constraint attributes:
// In XML
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text1"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="android.R.color.cyan"
<!-- This centers the view in parent -->
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="FIRST"/>
<TextView
android:id="@+id/text2"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="android.R.color.green"
<!-- This centers the view below text1 -->
app:layout_constraintStart_toStartOf="@id/text1"
app:layout_constraintEnd_toEndOf="@id/text1"
app:layout_constraintTop_toBottomOf="@id/text1"
android:text="SECOND"/>
</androidx.constraintlayout.widget.ConstraintLayout>
In Compose the IDs become references you create with createRefs(), and the constraints move into a constrainAs block on each composable’s Modifier:
// In Jetpack Compose ConstraintLayout
@Composable
fun ConstraintCompose() {
// these are the id references (very important)
val (text1, text2) = createRefs()
ConstraintLayout(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
) {
Text(
modifier = Modifier
.size(100.dp)
.background(color = Color.Cyan, shape = Shapes.large)
.constrainAs(text1) {
centerTo(parent)
},
text = "FIRST"
)
Text(
modifier = Modifier
.size(100.dp)
.background(color = Color.Green, shape = Shapes.large)
.constrainAs(text2) {
top.linkTo(anchor = text1.bottom)
start.linkTo(anchor = text1.start)
end.linkTo(anchor = text1.end)
},
text = "SECOND"
)
}
}
A few things map directly. createRefs() gives you the equivalent of XML IDs, and createRef() does the same for a single reference. You must create them before you reference them, so declare them at the top of the composable. centerTo(parent) is the shortcut for the four “center in parent” constraints you would otherwise write by hand. centerHorizontallyTo(parent) covers the horizontal pair only. Each side (top, bottom, start, end) has a linkTo. top.linkTo(text1.bottom) reads almost exactly like layout_constraintTop_toBottomOf="@id/text1", as a method call against an anchor instead of an attribute string.
The result looks like the screenshot below.

In this small example, ConstraintLayout for Jetpack Compose is more concise. With less code you achieve the same result you did with pure XML, and you get it type-checked at compile time instead of resolving string IDs at runtime.
You can drop other composables straight into a ConstraintLayout and constrain them the same way. I will cover that combination, plus guidelines and barriers, in a follow-up.
Happy coding.
David Cruz davthecoder.com

Si ya conoces ConstraintLayout del mundo XML, no tienes que tirar ese conocimiento por la borda en Jetpack Compose. Google publica una biblioteca constraintlayout-compose que te da el mismo modelo de anclaje (createRefs, constrainAs, linkTo) dentro de un @Composable. Eso sí, una advertencia honesta para 2026: en Compose, ConstraintLayout es una herramienta de legibilidad, no de rendimiento. El argumento de velocidad de la jerarquía plana, el que lo convirtió en la opción por defecto en XML, no se traslada. Recurre a él cuando un diseño se vuelve engorroso de expresar con Row, Column y Box, no por defecto.
Escribí este artículo por primera vez en 2021, cuando la versión de ConstraintLayout para Compose aún era un candidato a versión. La API ha aguantado bien desde entonces, así que casi todo esto sigue valiendo. He actualizado el planteamiento de cuándo usarlo y he limpiado la configuración.
¿Por qué seguir usando ConstraintLayout en Compose?
Durante años, ConstraintLayout fue una forma eficaz de crear interfaces complejas para nuestras aplicaciones Android. Con ConstraintLayout podías montar una pantalla completa sin anidar los contenedores de diseño de toda la vida, como LinearLayout, RelativeLayout y el resto. Una jerarquía de vistas plana era más rápida de medir y colocar, así que se volvió la opción por defecto para cualquier cosa que no fuera trivial.
Cuando Google publicó la primera versión estable de Jetpack Compose, cada desarrollador tuvo que explorar, aprender y adaptarse de golpe a una forma nueva de construir interfaces. Para desarrolladores como yo, renunciar a ConstraintLayout sonaba a no rotundo, y volver a aprender el diseño desde cero no era algo que me apeteciera.
Publicó una biblioteca que te deja usar ConstraintLayout dentro de Compose. Menuda alegría. En el sistema de Views, ConstraintLayout se ganó el sitio en parte por el rendimiento: aplanar una jerarquía profunda significaba menos pasadas de medida y de colocación. Compose gestiona por su cuenta, y con eficiencia, los árboles de diseño profundos, así que esa victoria concreta no se transfiere. Cuando tienes un puñado de vistas con relaciones del estilo «centra esto respecto al padre y luego cuelga aquello de su borde inferior», las restricciones se leen con más claridad que una pila de modificadores Box y Column anidados.
Cómo añadir ConstraintLayout a un proyecto de Compose
- Abre el
build.gradle(obuild.gradle.kts) del módulo donde viven el resto de dependencias de Compose. - Añade la dependencia
constraintlayout-compose. Usa la versión estable actual (1.1.1 a mediados de 2026, publicada en febrero de 2025):
implementation 'androidx.constraintlayout:constraintlayout-compose:1.1.1'
- Confirma la última versión estable en la página oficial de versiones antes de sincronizar: https://developer.android.com/jetpack/androidx/releases/constraintlayout
- Sincroniza Gradle y luego importa
ConstraintLayoutdesdeandroidx.constraintlayout.composedentro de tu archivo de composable.
Esa es toda la configuración. Es un artefacto distinto del clásico androidx.constraintlayout:constraintlayout que usabas en el mundo de Views, así que añadir uno no te da el otro.
¿Cómo se traduce un ConstraintLayout de XML a Compose?
El concepto de fondo es idéntico:
«el lado izquierdo de mi vista al izquierdo del padre, y la parte superior de mi vista a la inferior de otra vista…»
Pero ahora lo expresas en Kotlin, y puede parecer que estás construyendo vistas de toda la vida de forma programática:
val textView = TextView(context)
.... code ...
addView(textView)
Esa comparación va solo del modelo mental, no de la API real.

Un contenedor pelado tiene esta pinta:
// In XML
<androidx.constraintlayout.widget.ConstraintLayout>
// Code
</androidx.constraintlayout.widget.ConstraintLayout>
// In Jetpack Compose ConstraintLayout
@Composable
fun ConstraintCompose() {
ConstraintLayout {
// Code
}
}
Si quieres que el ConstraintLayout iguale al padre en anchura y altura:
// In XML
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
// Code
</androidx.constraintlayout.widget.ConstraintLayout>
// In Jetpack Compose ConstraintLayout
@Composable
fun ConstraintCompose() {
ConstraintLayout(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
) {
// Code
}
}
¿Cómo se restringen dos vistas entre sí?
En este ejemplo creamos dos vistas y colocamos la segunda respecto a la primera. En XML asignas identificadores y los referencias desde los atributos de restricción:
// In XML
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text1"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="android.R.color.cyan"
<!-- This centers the view in parent -->
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="FIRST"/>
<TextView
android:id="@+id/text2"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="android.R.color.green"
<!-- This centers the view below text1 -->
app:layout_constraintStart_toStartOf="@id/text1"
app:layout_constraintEnd_toEndOf="@id/text1"
app:layout_constraintTop_toBottomOf="@id/text1"
android:text="SECOND"/>
</androidx.constraintlayout.widget.ConstraintLayout>
En Compose los identificadores se vuelven referencias que creas con createRefs(), y las restricciones pasan a un bloque constrainAs en el Modifier de cada composable:
// In Jetpack Compose ConstraintLayout
@Composable
fun ConstraintCompose() {
// these are the id references (very important)
val (text1, text2) = createRefs()
ConstraintLayout(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
) {
Text(
modifier = Modifier
.size(100.dp)
.background(color = Color.Cyan, shape = Shapes.large)
.constrainAs(text1) {
centerTo(parent)
},
text = "FIRST"
)
Text(
modifier = Modifier
.size(100.dp)
.background(color = Color.Green, shape = Shapes.large)
.constrainAs(text2) {
top.linkTo(anchor = text1.bottom)
start.linkTo(anchor = text1.start)
end.linkTo(anchor = text1.end)
},
text = "SECOND"
)
}
}
Hay cosas que se corresponden de forma directa. createRefs() te da el equivalente de los identificadores XML, y createRef() hace lo mismo para una sola referencia. Tienes que crearlas antes de referenciarlas, así que decláralas al principio del composable. centerTo(parent) es el atajo de las cuatro restricciones de «centrar en el padre» que, si no, escribirías a mano. centerHorizontallyTo(parent) cubre solo el par horizontal. Cada lado (top, bottom, start, end) tiene un linkTo. top.linkTo(text1.bottom) se lee casi igual que layout_constraintTop_toBottomOf="@id/text1", como llamada a un método contra un ancla, en lugar de una cadena de atributo.
El resultado se parece a la captura de abajo.

En este ejemplo pequeño, ConstraintLayout para Jetpack Compose es más conciso. Con menos código consigues el mismo resultado que con XML puro, y lo tienes comprobado en tipos en tiempo de compilación, en lugar de resolver identificadores de cadena en tiempo de ejecución.
Puedes meter otros composables directamente en un ConstraintLayout y restringirlos igual. Esa combinación, más las guías y las barreras, la cubriré en una continuación.
¡Feliz programación!
David Cruz davthecoder.com
Loading comments…Cargando comentarios…