You write once and your code automatically adapts to any device. The library provides 12 different strategies, each with its own “growth formula”.
Uses: SHORTEST side of the screen
Best for: Padding, spacing, general sizes
Uses: Screen HEIGHT only
Best for: Component height, vertical lists
Uses: Screen WIDTH only
Best for: Horizontal components
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):
Result = Value × (Screen_Size / 300)
What it means:
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!
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:
When to use: ✅ Phone + tablet with one token set ✅ When SCALED grows too much
space*) — the number is the %Code:
Modifier.width(10.spaceW) // 10% width
Modifier.height(20.spaceSw) // 20% smallest side
Formula:
Result = (Percentage / 100) × Screen_Size
What it means:
When to use: ✅ Grids, columns ✅ Exact percentages of width, height, or smallest width
psdp / phdp / pwdp / pssp) — percent strategy, same naming as sdp / hdp / wdp / sspCode:
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 want | Typical extension | Idea |
|---|---|---|
SW-based (like sdp) |
16.psdp |
Design token + percent strategy |
| Height axis | 16.phdp |
Same pattern on height |
| Width axis | 16.pwdp |
Same pattern on width |
Text (like ssp) |
16.pssp |
Sp on smallest-width axis, percent rules |
Formula:
Without aspect ratio: same proportional idea as SCALED — roughly Value × (Axis / 300)
With `a`: percent strategy + aspect-ratio correction (library handles the curve)
What it means:
sdp / hdp / wdp / ssp — you keep design tokens (e.g. 16), not “16% of the screen.”space* in A) above — not 10.psdp.a, i, ia, Px, and inverters like SCALED — see the main README and 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
Formula:
Result = Value × (Size / 300)^0.75
What it means:
When to use: ✅ Icons ✅ Large displays (TVs, ultrawide)
Formula:
≤320 dp → 0.8×
≥768 dp → 1.2×
Between → linear interpolation
What it means:
When to use: ✅ Typography on mobile ✅ Stable spacing
Formula:
Diagonal = √(width² + height²)
Result = Value × (Diagonal / 611.63)
What it means:
When to use: ✅ Games ✅ Dashboards
Formula:
Result = Value × max(width_ratio, height_ratio)
What it means:
When to use: ✅ Backgrounds ✅ Hero sections
Formula:
Result = Value × min(width_ratio, height_ratio)
What it means:
When to use: ✅ Forms ✅ Reading layouts
Formula:
Result = Value + (Linear - Value) × 0.5
What it means:
When to use: ✅ Fine-tuning UI scaling
Formula:
Result = Value × (1 + 0.4 × ln(Size / 300))
What it means:
When to use: ✅ One token for all devices
Formula:
Perimeter = width + height
Result = Value × (Perimeter / 833)
What it means:
When to use: ✅ Cards ✅ Thumbnails
Formula:
Result = Value × (DPI / 160)
What it means:
When to use: ✅ Legacy bitmap assets
| Suffix | Meaning |
|---|---|
| (none) | Default |
a |
Aspect ratio adjustment |
i |
Ignore split-screen |
ia |
Both |
| Inverter | Behavior |
|---|---|
Ph |
Height in portrait |
Pw |
Width in portrait |
Lw |
Width in landscape |
Lh |
Height in landscape |
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
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)
}
}
}
You only need 3 strategies to start:
Modifiers:
a → aspect ratioi → ignore split-screenStart simple and scale when needed. 🚀
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 DpIdea: 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 sizeProblem: 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:
| Extension | Axis / meaning | Use case |
|---|---|---|
16.sem |
Smallest-width based | Toolbar, fixed rhythm |
16.hem |
Height-based | Vertical emphasis |
16.wem |
Width-based | Horizontal 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.
Idea: Instead of if (orientation == …) everywhere, use one call that picks the right branch.
| Family | Example (SCALED) | Plain English |
|---|---|---|
| Rotate | 80.sdpRotate(50, Orientation.LANDSCAPE) |
“Usually 80 sdp; in landscape use 50 (scaled).” |
| Mode | 30.sdpMode(200, UiModeType.TELEVISION) |
“Usually 30; on TV use 200 (scaled).” |
| Qualifier | 60.sdpQualifier(120, DpQualifier.SMALL_WIDTH, 600) |
“If smallest width ≥ 600, use 120; else 60.” |
| Screen | 70.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.
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.
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.
| Topic | Remember |
|---|---|
| Resize | Inside BoxWithConstraints; “largest size that still fits” |
| psdp / phdp / pwdp / pssp | Percent strategy, same naming style as sdp / hdp / wdp / ssp (see #3 B) |
| …Px | Float pixels for Canvas / custom drawing |
| sem / hem / wem | Screen-scaled Sp without following system font scale like ssp |
| Rotate / Mode / Qualifier / Screen | One-liner conditional sizing |
| scaledDp / scaledSp | Chain many rules, then .sdp / .ssp |
| mm / cm / inch | Real-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.