📚 Complete Guide: AppDimens Dynamic

August 28, 2026 · View on GitHub

Quick Introduction

You write once and your code automatically adapts to any device. The library provides 12 different strategies, each with its own “growth formula”.


Installation (v3.2.0)

implementation(platform("io.github.bodenberg:appdimens-dynamic-bom:3.2.0"))
implementation("io.github.bodenberg:appdimens-dynamic")
implementation("io.github.bodenberg:appdimens-dynamic-percent")
implementation("io.github.bodenberg:appdimens-dynamic-resize")

Without the BOM, pin :3.2.0 on each coordinate. Details: README · MODULES.md.

Strategy in this guideMaven module
Scaled (sdp / hdp / wdp / ssp)appdimens-dynamic
Percent, Power, Fluid, Auto, Diagonal, Fill, Fit, Interpolated, Logarithmic, Perimeter, Densityappdimens-dynamic-<name>
Resize (autoResize*)appdimens-dynamic-resize
Physical units (mm / cm / in)appdimens-dynamic-units

🎯 The 3 Core Pillars

1️⃣ SDP (Smallest Dimension Proportion) – The Smallest Side

Uses: SHORTEST side of the screen
Best for: Padding, spacing, general sizes

2️⃣ HDP (Height Dimension Proportion) – Height

Uses: Screen HEIGHT only
Best for: Component height, vertical lists

3️⃣ WDP (Width Dimension Proportion) – Width

Uses: Screen WIDTH only
Best for: Horizontal components

📊 The 12 Calculation Strategies

#1. SCALED (Default) ⭐

Reality check: In practice, Scaled is the strategy people use the most and it covers most everyday screens. Stick to plain sdp / hdp / wdp / ssp when you want simple proportional growth. Add a (aspect ratio), e.g. 16.sdpa or 16.sspa, when tall tablets or unusual aspect ratios need a smoother, refined adjustment — still Scaled, just with the extra flag.

Code:

// Compose
Modifier.padding(16.sdp)   // SDP
Modifier.height(100.hdp)   // HDP
Modifier.width(200.wdp)    // WDP
Text("Text", fontSize = 16.ssp)

// Views
DimenSdp.sdp(context, 16)

Formula (Simple):

$ \text{Result} = \text{Value} \times (\text{Screen\_Size} / 300) $

What it means:

  • 300 dp is the reference “normal” size
  • Larger screens → bigger values
  • Linear and predictable growth

Example:

Phone 360 dp:   16 × (360/300) = 19.2 px
Tablet 480 dp:  16 × (480/300) = 25.6 px
Tablet 600 dp:  16 × (600/300) = 32 px
TV 1200 dp:     16 × (1200/300) = 64 px

When to use: ✅ Default layouts ✅ Spacing and padding ✅ General sizes ✅ Start here!


#2. AUTO (Automatic) ⭐⭐

Code:

Modifier.padding(16.asdp)
Modifier.height(100.ahdp)
Modifier.width(200.awdp)
Text("Text", fontSize = 16.assp)

Formula:

Up to 480 dp:
  Same as SCALED

Above 480 dp:
  Result = (480/300) × Value + 0.4 × ln(1 + (Size - 480) / 300)

What it means:

  • Phones → behaves like SCALED
  • Tablets → much slower growth
  • Prevents oversized UI on large screens

When to use: ✅ Phone + tablet with one token set ✅ When SCALED grows too much


#3. PERCENT

A) Screen share (space*) — the number is the %

Code:

Modifier.width(10.spaceW)   // 10% width
Modifier.height(20.spaceSw) // 20% smallest side

Formula:

$ \text{Result} = (\text{Percentage} / 100) \times \text{Screen\_Size} $

What it means:

  • Always keeps the same percentage of the chosen axis
  • Fully proportional layouts

When to use: ✅ Grids, columns ✅ Exact percentages of width, height, or smallest width


B) Tokens like Scaled (psdp / phdp / pwdp / pssp) — percent strategy, same naming as sdp / hdp / wdp / ssp

Code:

import com.appdimens.dynamic.compose.percent.psdp
import com.appdimens.dynamic.compose.percent.phdp
import com.appdimens.dynamic.compose.percent.pwdp
import com.appdimens.dynamic.compose.percent.pssp

Modifier.padding(12.psdp)
Modifier.height(40.phdp)
Modifier.width(30.pwdp)
Text("Hi", fontSize = 14.pssp)
You wantTypical extensionIdea
SW-based (like sdp)16.psdpDesign token + percent strategy
Height axis16.phdpSame pattern on height
Width axis16.pwdpSame pattern on width
Text (like ssp)16.psspSp on smallest-width axis, percent rules

Formula:

``Withoutaspectratio:sameproportionalideaasSCALED—roughlyValue×(Axis/300)With\text{Without} \text{aspect} \text{ratio}: \text{same} \text{proportional} \text{idea} \text{as} \text{SCALED} — \text{roughly} \text{Value} \times (\text{Axis} / 300) \text{With}a`: percent strategy + aspect-ratio correction (library handles the curve)


**What it means:**

* Same **suffix mental model** as **`sdp` / `hdp` / `wdp` / `ssp`** — you keep **design tokens** (e.g. `16`), not “16% of the screen.”
* For **literal** “10% of width”, use **`space*`** in **A)** above — not `10.psdp`.
* You can still add **`a`**, **`i`**, **`ia`**, **Px**, and **inverters** like SCALED — see the main README and [DOCUMENTATION/COMPOSE-API-CONVENTIONS.md](DOCUMENTATION/COMPOSE-API-CONVENTIONS.md).

**When to use:**
✅ You want **percent** scaling rules but **`sdp`-style** names across the codebase  
✅ Mixing with **`space*`** when some sizes are “% of axis” and others are tokens  

---

### **#4. POWER (Sublinear Growth)**

**Formula:**

Result = Value × (Size / 300)^0.75


**What it means:**

* Slower than linear scaling
* Prevents oversized elements

**When to use:**
✅ Icons
✅ Large displays (TVs, ultrawide)

---

### **#5. FLUID (Comfort Zone)**

**Formula:**

≤320 dp → 0.8×
≥768 dp → 1.2×
Between → linear interpolation


**What it means:**

* Keeps values within a stable range
* Avoids big jumps on phones

**When to use:**
✅ Typography on mobile
✅ Stable spacing

---

### **#6. DIAGONAL**

**Formula:**

Diagonal = √(width² + height²) Result = Value × (Diagonal / 611.63)


**What it means:**

* Uses total screen size
* More “real” scaling

**When to use:**
✅ Games
✅ Dashboards

---

### **#7. FILL (CSS “cover”)**

**Formula:**

Result = Value × max(width_ratio, height_ratio)


**What it means:**

* Uses the **largest axis**
* Fills the screen

**When to use:**
✅ Backgrounds
✅ Hero sections

---

### **#8. FIT (CSS “contain”)**

**Formula:**

Result = Value × min(width_ratio, height_ratio)


**What it means:**

* Uses the **smallest axis**
* Prevents overflow

**When to use:**
✅ Forms
✅ Reading layouts

---

### **#9. INTERPOLATED**

**Formula:**

Result = Value + (Linear - Value) × 0.5


**What it means:**

* 50% fixed + 50% scalable
* Smooth growth

**When to use:**
✅ Fine-tuning UI scaling

---

### **#10. LOGARITHMIC**

**Formula:**

Result = Value × (1 + 0.4 × ln(Size / 300))


**What it means:**

* Very smooth growth
* Works across all screen sizes

**When to use:**
✅ One token for all devices

---

### **#11. PERIMETER**

**Formula:**

``$
\text{Perimeter} = \text{width} + \text{height}
\text{Result} = \text{Value}  \times  (\text{Perimeter} / 833)
$``

**What it means:**

* Uses both axes (sum)
* Balanced scaling

**When to use:**
✅ Cards
✅ Thumbnails

---

### **#12. DENSITY (DPI-based)**

**Formula:**

``$
\text{Result} = \text{Value}  \times  (\text{DPI} / 160)
$``

**What it means:**

* Based on pixel density
* Not screen size

**When to use:**
✅ Legacy bitmap assets

---

## 🔧 Modifiers (Suffixes)

| Suffix   | Meaning                 |
| -------- | ----------------------- |
| *(none)* | Default                 |
| `a`      | Aspect ratio adjustment |
| `i`      | Ignore split-screen     |
| `ia`     | Both                    |

---

## 🔄 Orientation Inverters

| Inverter | Behavior            |
| -------- | ------------------- |
| `Ph`     | Height in portrait  |
| `Pw`     | Width in portrait   |
| `Lw`     | Width in landscape  |
| `Lh`     | Height in landscape |

---

## 📱 Quick Comparison

Need → Use

Default → SCALED
Phone + Tablet → AUTO
Exact % → PERCENT
Slow growth → POWER
Stable phone UI → FLUID
Full screen → FILL
No overflow → FIT
Balanced → INTERPOLATED
All devices → LOGARITHMIC
Cards → PERIMETER
Legacy → DENSITY


---

## 🚀 Practical Example

```kotlin
Card(
    modifier = Modifier
        .fillMaxWidth()
        .padding(
            horizontal = 16.sdp,
            vertical = 12.asdp
        )
) {
    Column(
        modifier = Modifier.padding(16.sdp)
    ) {
        Text(
            "My Card",
            fontSize = 24.ssp
        )

        Text(
            "Card description",
            fontSize = 16.ssp
        )

        Button(
            modifier = Modifier
                .height(48.sdp)
                .fillMaxWidth()
        ) {
            Text("Click", fontSize = 16.ssp)
        }
    }
}

💡 Final Tips

  1. Start with SCALED
  2. If it grows too much → use AUTO
  3. For universal tokens → use LOGARITHMIC
  4. For backgrounds → use FILL
  5. For reading → use FIT

🎓 Quick Recap

You only need 3 strategies to start:

  1. SCALED
  2. AUTO
  3. PERCENT

Modifiers:

  • a → aspect ratio
  • i → ignore split-screen

Start simple and scale when needed. 🚀


📐 Resize — sizes that fit inside a box (not from screen formula alone)

Idea in plain words: Sometimes a number (font size, icon side, width) must be as large as possible but still fit inside the space the parent gives you — for example a long title in a card. Resize does not use the “screen ÷ 300” formula by itself; it tries several sizes (like steps on a ladder) until it finds the biggest one that still fits.

When to use: ✅ Headlines that must not overflow
✅ Icons or squares that should use “all the room” in a cell
✅ Anything where the parent size matters more than the global screen size

Rule: Call these functions only inside BoxWithConstraints { ... } — that block tells you how much width/height you really have.

Example — text that shrinks until it fits:

import androidx.compose.foundation.layout.BoxWithConstraints
import com.appdimens.dynamic.compose.resize.autoResizeTextSp

BoxWithConstraints(Modifier.fillMaxWidth()) {
    val fontSize = autoResizeTextSp(
        text = "Very long product name that might not fit",
        minSp = 12,
        maxSp = 28,
        stepSp = 1,
        maxLines = 2,
    )
    Text(
        text = "Very long product name that might not fit",
        fontSize = fontSize,
        maxLines = 2,
    )
}

What happens: The library tests 28 sp, 27 sp, 26 sp… down to 12 sp (or your stepSp) and picks the largest size where the text still fits in maxLines and the box.

Example — largest square that fits:

import com.appdimens.dynamic.compose.resize.autoResizeSquareSize

BoxWithConstraints(Modifier.fillMaxSize()) {
    val side = autoResizeSquareSize(min = 24, max = 120, step = 4)
    Box(Modifier.size(side)) { /* icon or avatar */ }
}

There are also helpers for width only, height only, and ranges based on % of the inner box — same idea: pick the biggest step that still fits.


📏 …Px extensions — when you need pixels, not Dp

Idea: 16.sdp gives a Dp for layouts. Sometimes you need a Float in px (Canvas, custom drawing, old interop).

Pattern: Add Px to the name.

val strokeWidthPx = 2f.sdpPx      // Float in px, SDP-based
val paddingPx = 16.hdpPx          // height axis, px

Same suffixes (a, i, ia, inverters) exist on many Px properties — check the API for your strategy.


🔤 sem / hem / wem — scalable Sp that ignores system font size

Problem: 16.ssp scales with the screen and usually follows the user’s font scale (Accessibility). Sometimes you want screen-based size but not to grow with “larger text” in settings.

Fix: “Fixed em” style extensions on the scaled strategy:

ExtensionAxis / meaningUse case
16.semSmallest-width basedToolbar, fixed rhythm
16.hemHeight-basedVertical emphasis
16.wemWidth-basedHorizontal emphasis
Text("Always same visual weight vs layout", fontSize = 14.sem)

You still get responsive sizing vs the device; you just don’t tie this particular text to system font scale the way ssp does.


🎛️ Facilitators — one value normally, another in special cases

Idea: Instead of if (orientation == …) everywhere, use one call that picks the right branch.

FamilyExample (SCALED)Plain English
Rotate80.sdpRotate(50, Orientation.LANDSCAPE)“Usually 80 sdp; in landscape use 50 (scaled).”
Mode30.sdpMode(200, UiModeType.TELEVISION)“Usually 30; on TV use 200 (scaled).”
Qualifier60.sdpQualifier(120, DpQualifier.SMALL_WIDTH, 600)“If smallest width ≥ 600, use 120; else 60.”
Screen70.sdpScreen(150, UiModeType.TELEVISION, DpQualifier.SMALL_WIDTH, 600)Combines mode + width threshold in one rule.

Many overloads accept Int, Dp, or “plain” variants — see the main README. Other strategies use the same names with their prefix (e.g. asdpRotate, pwsdpMode).

Plain with Dp / TextUnit (no extra scaling): When both the base and the alternate are already from the same strategy (e.g. 30.sdp and 20.sdp), use the overloads that take Dp or TextUnit as the alternate — only the branch (orientation, UI mode, qualifier, …) runs:

// Dp: both sides already scaled
val side = 30.sdp.sdpRotatePlain(20.sdp)

// Sp: both sides already scaled
Text("Hi", fontSize = 16.ssp.sspRotatePlain(12.ssp))

Nested facilitators: You can chain them; the effective order is how you nest the expression (evaluate outside → inside). For long chains, keep using Dp / TextUnit Plain alternates to avoid scaling the receiver or the alternate more than once.

Not the same as .screen: 100.scaledDp().screen(…).screen(…).sdp uses priorities inside DimenScaled — that order is not the same rule as “who is outside” in nested sdpRotatePlain / sdpModePlain calls. See COMPOSE-API-CONVENTIONS.md.


🧱 Builders — scaledDp() / scaledSp() (chain many rules)

Idea: Start from a number and chain options: aspect ratio, ignore multi-window, then several screen rules (TV, large sw, landscape…). At the end read .sdp, .hdp, .wdp or Sp.

val padding = 16.scaledDp()
    .aspectRatio(true)
    .ignoreMultiWindows(true)
    .screen(UiModeType.TELEVISION, 40)
    .screen(DpQualifier.SMALL_WIDTH, 600, 24)
    .sdp

Beginner tip: Use simple 16.sdp first; move to builders when one component needs many conditions.


📦 Physical units — real-world size (mm, cm, inch)

Idea: Say “10 mm on screen” instead of guessing dp. The library converts using the device’s density (so it’s only approximate physical size, like any Android dp).

Typical use: Rulers, print-like layouts, specs from design in mm or inch.

Easy Compose pattern: Use DimenPhysicalUnits.toMm / toCm / toInch with LocalResources — they return a Float in dp. Then use .dp:

import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.unit.dp
import com.appdimens.dynamic.compose.DimenPhysicalUnits

@Composable
fun RulerBar() {
    val resources = LocalResources.current
    val widthDp = DimenPhysicalUnits.toMm(10f, resources)   // 10 mm → dp
    val heightDp = DimenPhysicalUnits.toInch(0.25f, resources)
    Modifier
        .width(widthDp.dp)
        .height(heightDp.dp)
}

Inside the same @Composable, you can also use the shorthand 10f.mm, 2.5f.cm, 1f.inch if you import the extensions from DimenPhysicalUnits (see DOCUMENTATION/physical-units.md).

There are helpers in com.appdimens.dynamic.code.units for non-Compose code, plus radius from diameter, etc. Full table: DOCUMENTATION/physical-units.md.


✅ Checklist — “Did I miss something?”

TopicRemember
ResizeInside BoxWithConstraints; “largest size that still fits”
psdp / phdp / pwdp / psspPercent strategy, same naming style as sdp / hdp / wdp / ssp (see #3 B)
…PxFloat pixels for Canvas / custom drawing
sem / hem / wemScreen-scaled Sp without following system font scale like ssp
Rotate / Mode / Qualifier / ScreenOne-liner conditional sizing
scaledDp / scaledSpChain many rules, then .sdp / .ssp
mm / cm / inchReal-world units → dp on screen

When in doubt, stay on SCALED (sdp / hdp / wdp) and add these tools only when a real layout problem appears.