suiss/uim · React Native Component
Amount Input
A large, locale-formatted money display with an on-screen keypad for entering an amount.
Amount Input is an open-source mobile component from suiss/uim for React Native and Expo, with SwiftUI and Jetpack Compose versions built from the same design tokens. The @esuiss/uim CLI copies its source into your project, so you own the code.


import { AmountInput } from "@/components/ui/amount-input"
export function AmountInputExample() {
return (
<AmountInput currency="USD" locale="en-US" />
)
}import EsuissUI
import SwiftUI
struct AmountInputExample: View {
var body: some View {
AmountInputDemo(currency: "USD", locale: "en-US")
}
}
struct AmountInputDemo: View {
let currency: String
let locale: String
var max: Double?
@State private var value: String
init(currency: String, locale: String, initial: String = "", max: Double? = nil) {
self.currency = currency
self.locale = locale
self.max = max
_value = State(initialValue: initial)
}
var body: some View {
AmountInput(value: $value, currency: currency, max: max)
.environment(\.locale, Locale(identifier: locale))
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.AmountInput
import java.util.Locale
@Composable
fun AmountInputExample() {
AmountInputStory("USD", "en-US")
}
@Composable
fun AmountInputStory(
currency: String,
locale: String,
defaultValue: String = "",
max: Double? = null,
enabled: Boolean = true,
) {
var value by rememberSaveable { mutableStateOf(defaultValue) }
AmountInput(
modifier = Modifier.fillMaxWidth(),
value = value,
onValueChange = { next, _ -> value = next },
currency = currency,
locale = Locale.forLanguageTag(locale),
max = max,
enabled = enabled,
)
}AmountInput combines a big formatted amount with a Keypad. The amount is formatted with Intl.NumberFormat for the given locale and currency, the currency symbol is drawn smaller, and the font size steps down as the number gets longer.
The decimal key appears only when the currency allows fractions, using the locale's decimal separator. A key that would exceed the limits is rejected with a shake and an error haptic; accepted keys play a selection haptic.
Installation
$ pnpm dlx @esuiss/uim@latest add amount-input$ pnpm dlx @esuiss/uim@latest add amount-input --platform swift$ pnpm dlx @esuiss/uim@latest add amount-input --platform kotlinThe CLI also adds what it builds on: Keypad, Text.
Manual
- Install the dependencies:
npx expo install cn react-native-reanimated react-native-worklets-
Add the pieces it builds on: Keypad, Text, and the primitives
components/primitives/haptics.tsx. -
Copy the source into
components/ui/amount-input.tsx:
import { cn } from "cn"
import * as React from "react"
import { View } from "react-native"
import {
ReduceMotion,
useAnimatedStyle,
useSharedValue,
withSequence,
withTiming,
} from "react-native-reanimated"
import { useHaptics } from "@/components/primitives/haptics"
import { AnimatedView } from "@/components/ui/animated-view"
import { Keypad, type KeypadKeyValue } from "@/components/ui/keypad"
import { Text } from "@/components/ui/text"
type AmountPart = { type: string; value: string }- Update the import paths to match your project.
- Install the dependencies:
npx expo install cn react-native-reanimated react-native-worklets-
Add the pieces it builds on: Keypad, Text, and the primitives
components/primitives/haptics.tsx. -
Copy the source into
components/ui/amount-input.tsx:
import { cn } from "cn"
import * as React from "react"
import { View } from "react-native"
import {
ReduceMotion,
useAnimatedStyle,
useSharedValue,
withSequence,
withTiming,
} from "react-native-reanimated"
import { useHaptics } from "@/components/primitives/haptics"
import { AnimatedView } from "@/components/ui/animated-view"
import { Keypad, type KeypadKeyValue } from "@/components/ui/keypad"
import { Text } from "@/components/ui/text"
type AmountPart = { type: string; value: string }- Update the import paths to match your project.
- Install the dependencies:
npx expo install react-native-reanimated react-native-worklets-
Add the pieces it builds on: Keypad, Text, and the primitives
components/primitives/haptics.tsx. -
Copy the source into
components/ui/amount-input.tsx:
import * as React from "react"
import {
type StyleProp,
StyleSheet,
type TextStyle,
View,
type ViewStyle,
} from "react-native"
import {
ReduceMotion,
useAnimatedStyle,
useSharedValue,
withSequence,
withTiming,
} from "react-native-reanimated"
import { useHaptics } from "@/components/primitives/haptics"- Update the import paths to match your project.
-
Target iOS 17 or later.
-
Add the Swift package to your app:
.package(url: "https://github.com/phosphor-icons/swift", from: "2.1.0")- Copy the theme files every view uses:
import SwiftUI
import UIKit
struct RGBA: Sendable {
let red: CGFloat
let green: CGFloat
let blue: CGFloat
let alpha: CGFloat
init(_ hex: UInt32, _ alpha: CGFloat = 1) {
red = CGFloat((hex >> 16) & 0xFF) / 255
green = CGFloat((hex >> 8) & 0xFF) / 255
blue = CGFloat(hex & 0xFF) / 255
self.alpha = alpha
}import SwiftUI
extension Color {
public static let surface = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))
public static let surfaceSecondary = Color.adaptive(light: .init(0xF5F5F7), dark: .init(0x161617))
public static let surfaceTertiary = Color.adaptive(light: .init(0xE8E8ED), dark: .init(0x2C2C2E))
public static let surfaceRaised = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x1D1D1F))
public static let label = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
public static let labelSecondary = Color.adaptive(light: .init(0x6E6E73), dark: .init(0xA1A1A6))
public static let labelTertiary = Color.adaptive(light: .init(0x86868B), dark: .init(0x86868B))
public static let labelQuaternary = Color.adaptive(light: .init(0xD2D2D7), dark: .init(0x424245))
public static let control = Color.adaptive(light: .init(0x000000, 0.04), dark: .init(0xFFFFFF, 0.1))
public static let controlPressed = Color.adaptive(light: .init(0x000000, 0.12), dark: .init(0xFFFFFF, 0.22))
public static let controlSelected = Color.adaptive(light: .init(0x1D1D1F), dark: .init(0xF5F5F7))
public static let controlDisabled = Color.adaptive(light: .init(0x000000, 0.03), dark: .init(0xFFFFFF, 0.05))
public static let onControlSelected = Color.adaptive(light: .init(0xFFFFFF), dark: .init(0x000000))- Copy the source and every file it uses:
import SwiftUI
struct AmountFormat {
let locale: Locale
let currency: String?
let fractionDigits: Int
let decimalSeparator: String
let prefix: String
let suffix: String
init(locale: Locale, currency: String?, maxFractionDigits: Int? = nil) {
let formatter = NumberFormatter()
formatter.locale = locale
formatter.numberStyle = currency == nil ? .decimal : .currency
if let currency { formatter.currencyCode = currency }
self.locale = localeimport PhosphorSwift
import SwiftUI
extension Shadow {
static let control = Shadow(color: .black.opacity(0.08), radius: 0.5, y: 0.5)
static let thumb = Shadow(color: .black.opacity(0.12), radius: 1.5, y: 1)
}
struct SelectionIndicator<Mark: View>: View {
let checked: Bool
let pressed: Bool
let cornerRadius: CGFloat
let mark: Mark
@Environment(\.isEnabled) private var isEnabled
@Environment(\.isInvalid) private var invalidimport SwiftUI
extension EnvironmentValues {
@Entry public var isInvalid = false
@Entry var fieldInset = false
@Entry var fieldGroupInset = false
@Entry var fieldSetInset = false
}
extension View {
public func invalid(_ isInvalid: Bool = true) -> some View {
environment(\.isInvalid, isInvalid)
}
}
public enum FieldOrientation: CaseIterable, Sendable {import SwiftUI
extension EnvironmentValues {
@Entry public var hapticsEnabled = true
}
extension View {
public func haptics(_ enabled: Bool) -> some View {
environment(\.hapticsEnabled, enabled)
}
func hapticFeedback<Trigger: Equatable>(_ feedback: SensoryFeedback, trigger: Trigger) -> some View {
modifier(HapticFeedbackModifier(feedback: feedback, trigger: trigger) { _, _ in true })
}
func hapticFeedback<Trigger: Equatable>(import PhosphorSwift
import SwiftUI
public enum KeypadKey: Hashable, Sendable {
case digit(Int)
case decimal
case delete
case biometric
}
public enum KeypadAccessory {
case none
case decimal(String, label: LocalizedStringKey = "Decimal separator")
case biometric(Image, label: LocalizedStringKey = "Use biometrics")
}import SwiftUI
public struct Separator: View {
let orientation: Axis
public init(_ orientation: Axis = .horizontal) {
self.orientation = orientation
}
public var body: some View {
Rectangle()
.fill(Color.separator)
.frame(width: orientation == .vertical ? 1 : nil, height: orientation == .horizontal ? 1 : nil)
.frame(maxWidth: orientation == .horizontal ? .infinity : nil, maxHeight: orientation == .vertical ? .infinity : nil)
.accessibilityHidden(true)
}import SwiftUI
public enum TextVariant: CaseIterable, Sendable {
case `default`, h1, h2, h3, h4, p, blockquote, code, lead, large, small, muted
var style: TextStyle {
switch self {
case .h1: .xl3
case .h2: .xl2
case .h3: .xl
case .h4, .lead, .large: .lg
case .code, .small, .muted: .sm
default: .base
}
}- Add the files to your app target.
- Set
minSdkto 24 or higher and add the dependencies:
implementation(platform("androidx.compose:compose-bom:2026.09.00"))
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.ui:ui")
implementation("com.adamglin:phosphor-icon-android:1.0.0")- Copy the theme files every view uses:
package com.esuiss.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.dropShadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.shadow.Shadow as DropShadow
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.LineHeightStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffsetpackage com.esuiss.ui.theme
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Immutable
data class Colors(
val surface: Color,
val surfaceSecondary: Color,
val surfaceTertiary: Color,
val surfaceRaised: Color,
val label: Color,
val labelSecondary: Color,
val labelTertiary: Color,
val labelQuaternary: Color,- Copy the source and every file it uses:
package com.esuiss.ui.components
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignmentpackage com.esuiss.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShapepackage com.esuiss.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Immutablepackage com.esuiss.ui.components
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
val LocalContentStyle = compositionLocalOf { TextStyle.Default }
val LocalContentColor = compositionLocalOf { Color.Unspecified }
val LocalIconSize = compositionLocalOf { 20.dp }package com.esuiss.ui.components
import android.provider.Settings
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.text.selection.TextSelectionColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Sizepackage com.esuiss.ui.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.hapticfeedback.HapticFeedback
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
val LocalHapticsEnabled = staticCompositionLocalOf { true }
@Composable
fun ProvideHaptics(enabled: Boolean, content: @Composable () -> Unit) {
CompositionLocalProvider(LocalHapticsEnabled provides enabled, content = content)
}package com.esuiss.ui.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Dp
import com.esuiss.ui.theme.EsuissTheme
@Composable
fun Icon(
imageVector: ImageVector,package com.esuiss.ui.components
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.keyframes
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.BasicText- Update the
com.esuiss.uipackage names to match your project.
Usage
import { AmountInput } from "@/components/ui/amount-input"<AmountInput currency="USD" locale="en-US" />The value is a plain numeric string such as "1250.5". Use defaultValue for uncontrolled state, or value with onValueChange, which receives the string and the parsed number.
Fraction digits default to the currency's own precision (for example 0 for JPY); override with maxFractionDigits. Use max to cap the amount. Input is limited to 15 digits.
Pass deleteLabel and decimalLabel to localize the keypad's spoken labels.
Examples
Filled
Starts with a value through defaultValue.


import { AmountInput } from "@/components/ui/amount-input"
export function AmountInputFilledExample() {
return (
<AmountInput currency="USD" locale="en-US" defaultValue="1250.5" />
)
}import EsuissUI
import SwiftUI
struct AmountInputFilledExample: View {
var body: some View {
AmountInputDemo(currency: "USD", locale: "en-US", initial: "1250.5")
}
}
struct AmountInputDemo: View {
let currency: String
let locale: String
var max: Double?
@State private var value: String
init(currency: String, locale: String, initial: String = "", max: Double? = nil) {
self.currency = currency
self.locale = locale
self.max = max
_value = State(initialValue: initial)
}
var body: some View {
AmountInput(value: $value, currency: currency, max: max)
.environment(\.locale, Locale(identifier: locale))
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.AmountInput
import java.util.Locale
@Composable
fun AmountInputFilledExample() {
AmountInputStory("USD", "en-US", "1250.5")
}
@Composable
fun AmountInputStory(
currency: String,
locale: String,
defaultValue: String = "",
max: Double? = null,
enabled: Boolean = true,
) {
var value by rememberSaveable { mutableStateOf(defaultValue) }
AmountInput(
modifier = Modifier.fillMaxWidth(),
value = value,
onValueChange = { next, _ -> value = next },
currency = currency,
locale = Locale.forLanguageTag(locale),
max = max,
enabled = enabled,
)
}Controlled
Controlled with value and onValueChange.


import * as React from "react"
import { View } from "react-native"
import { AmountInput } from "@/components/ui/amount-input"
import { Text } from "@/components/ui/text"
function ControlledDemo() {
const [value, setValue] = React.useState("")
const [amount, setAmount] = React.useState(0)
return (
<View className="items-center gap-4">
<AmountInput
currency="USD"
locale="en-US"
value={value}
onValueChange={(next, nextAmount) => {
setValue(next)
setAmount(nextAmount)
}}
/>
<Text className="text-sm text-label-secondary">
{`value "${value}" · amount ${amount}`}
</Text>
</View>
)
}
export function AmountInputControlledExample() {
return (
<ControlledDemo />
)
}import EsuissUI
import SwiftUI
struct AmountInputControlledDemo: View {
@State private var value = ""
@State private var amount: Double = 0
var body: some View {
VStack(spacing: 16) {
AmountInput(value: $value, currency: "USD") { _, next in
amount = next
}
.environment(\.locale, Locale(identifier: "en-US"))
Text(verbatim: "value \"\(value)\" · amount \(amountText)")
.textStyle(.sm)
.foregroundStyle(Color.labelSecondary)
}
.frame(maxWidth: .infinity)
}
var amountText: String {
amount.rounded() == amount && abs(amount) < 1e15 ? String(Int64(amount)) : String(amount)
}
}import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.AmountInput
import com.esuiss.ui.components.Text
import com.esuiss.ui.theme.EsuissTheme
import com.esuiss.ui.theme.Typography
import java.util.Locale
@Composable
fun AmountInputControlled() {
var value by rememberSaveable { mutableStateOf("") }
var amount by rememberSaveable { mutableDoubleStateOf(0.0) }
Column(
Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AmountInput(
modifier = Modifier.fillMaxWidth(),
value = value,
onValueChange = { next, nextAmount ->
value = next
amount = nextAmount
},
currency = "USD",
locale = Locale.forLanguageTag("en-US"),
)
val shown = if (amount % 1.0 == 0.0) amount.toLong().toString() else amount.toString()
Text(
"value \"$value\" · amount $shown",
style = Typography.sm.copy(color = EsuissTheme.colors.labelSecondary),
)
}
}Long amount
A long amount that shrinks to fit.


import { AmountInput } from "@/components/ui/amount-input"
export function AmountInputLongAmountExample() {
return (
<AmountInput currency="USD" locale="en-US" defaultValue="98765432109.99" />
)
}import EsuissUI
import SwiftUI
struct AmountInputLongAmountExample: View {
var body: some View {
AmountInputDemo(currency: "USD", locale: "en-US", initial: "98765432109.99")
}
}
struct AmountInputDemo: View {
let currency: String
let locale: String
var max: Double?
@State private var value: String
init(currency: String, locale: String, initial: String = "", max: Double? = nil) {
self.currency = currency
self.locale = locale
self.max = max
_value = State(initialValue: initial)
}
var body: some View {
AmountInput(value: $value, currency: currency, max: max)
.environment(\.locale, Locale(identifier: locale))
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.AmountInput
import java.util.Locale
@Composable
fun AmountInputLongAmountExample() {
AmountInputStory("USD", "en-US", "98765432109.99")
}
@Composable
fun AmountInputStory(
currency: String,
locale: String,
defaultValue: String = "",
max: Double? = null,
enabled: Boolean = true,
) {
var value by rememberSaveable { mutableStateOf(defaultValue) }
AmountInput(
modifier = Modifier.fillMaxWidth(),
value = value,
onValueChange = { next, _ -> value = next },
currency = currency,
locale = Locale.forLanguageTag(locale),
max = max,
enabled = enabled,
)
}Euro
EUR in the de-DE locale with a comma decimal separator.


import { AmountInput } from "@/components/ui/amount-input"
export function AmountInputEuroExample() {
return (
<AmountInput currency="EUR" locale="de-DE" defaultValue="1234.5" />
)
}import EsuissUI
import SwiftUI
struct AmountInputEuroExample: View {
var body: some View {
AmountInputDemo(currency: "EUR", locale: "de-DE", initial: "1234.5")
}
}
struct AmountInputDemo: View {
let currency: String
let locale: String
var max: Double?
@State private var value: String
init(currency: String, locale: String, initial: String = "", max: Double? = nil) {
self.currency = currency
self.locale = locale
self.max = max
_value = State(initialValue: initial)
}
var body: some View {
AmountInput(value: $value, currency: currency, max: max)
.environment(\.locale, Locale(identifier: locale))
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.AmountInput
import java.util.Locale
@Composable
fun AmountInputEuroExample() {
AmountInputStory("EUR", "de-DE", "1234.5")
}
@Composable
fun AmountInputStory(
currency: String,
locale: String,
defaultValue: String = "",
max: Double? = null,
enabled: Boolean = true,
) {
var value by rememberSaveable { mutableStateOf(defaultValue) }
AmountInput(
modifier = Modifier.fillMaxWidth(),
value = value,
onValueChange = { next, _ -> value = next },
currency = currency,
locale = Locale.forLanguageTag(locale),
max = max,
enabled = enabled,
)
}Turkish lira
TRY in the tr-TR locale.


import { AmountInput } from "@/components/ui/amount-input"
export function AmountInputTurkishLiraExample() {
return (
<AmountInput currency="TRY" locale="tr-TR" defaultValue="2500.75" />
)
}import EsuissUI
import SwiftUI
struct AmountInputTurkishLiraExample: View {
var body: some View {
AmountInputDemo(currency: "TRY", locale: "tr-TR", initial: "2500.75")
}
}
struct AmountInputDemo: View {
let currency: String
let locale: String
var max: Double?
@State private var value: String
init(currency: String, locale: String, initial: String = "", max: Double? = nil) {
self.currency = currency
self.locale = locale
self.max = max
_value = State(initialValue: initial)
}
var body: some View {
AmountInput(value: $value, currency: currency, max: max)
.environment(\.locale, Locale(identifier: locale))
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.AmountInput
import java.util.Locale
@Composable
fun AmountInputTurkishLiraExample() {
AmountInputStory("TRY", "tr-TR", "2500.75")
}
@Composable
fun AmountInputStory(
currency: String,
locale: String,
defaultValue: String = "",
max: Double? = null,
enabled: Boolean = true,
) {
var value by rememberSaveable { mutableStateOf(defaultValue) }
AmountInput(
modifier = Modifier.fillMaxWidth(),
value = value,
onValueChange = { next, _ -> value = next },
currency = currency,
locale = Locale.forLanguageTag(locale),
max = max,
enabled = enabled,
)
}Yen
JPY, which has no decimal key.


import { AmountInput } from "@/components/ui/amount-input"
export function AmountInputYenExample() {
return (
<AmountInput currency="JPY" locale="ja-JP" defaultValue="15000" />
)
}import EsuissUI
import SwiftUI
struct AmountInputYenExample: View {
var body: some View {
AmountInputDemo(currency: "JPY", locale: "ja-JP", initial: "15000")
}
}
struct AmountInputDemo: View {
let currency: String
let locale: String
var max: Double?
@State private var value: String
init(currency: String, locale: String, initial: String = "", max: Double? = nil) {
self.currency = currency
self.locale = locale
self.max = max
_value = State(initialValue: initial)
}
var body: some View {
AmountInput(value: $value, currency: currency, max: max)
.environment(\.locale, Locale(identifier: locale))
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.AmountInput
import java.util.Locale
@Composable
fun AmountInputYenExample() {
AmountInputStory("JPY", "ja-JP", "15000")
}
@Composable
fun AmountInputStory(
currency: String,
locale: String,
defaultValue: String = "",
max: Double? = null,
enabled: Boolean = true,
) {
var value by rememberSaveable { mutableStateOf(defaultValue) }
AmountInput(
modifier = Modifier.fillMaxWidth(),
value = value,
onValueChange = { next, _ -> value = next },
currency = currency,
locale = Locale.forLanguageTag(locale),
max = max,
enabled = enabled,
)
}With max
Capped at 500 with max; larger entries are rejected.


import { AmountInput } from "@/components/ui/amount-input"
export function AmountInputWithMaxExample() {
return (
<AmountInput currency="USD" locale="en-US" max={500} />
)
}import EsuissUI
import SwiftUI
struct AmountInputWithMaxExample: View {
var body: some View {
AmountInputDemo(currency: "USD", locale: "en-US", max: 500)
}
}
struct AmountInputDemo: View {
let currency: String
let locale: String
var max: Double?
@State private var value: String
init(currency: String, locale: String, initial: String = "", max: Double? = nil) {
self.currency = currency
self.locale = locale
self.max = max
_value = State(initialValue: initial)
}
var body: some View {
AmountInput(value: $value, currency: currency, max: max)
.environment(\.locale, Locale(identifier: locale))
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.AmountInput
import java.util.Locale
@Composable
fun AmountInputWithMaxExample() {
AmountInputStory("USD", "en-US", max = 500.0)
}
@Composable
fun AmountInputStory(
currency: String,
locale: String,
defaultValue: String = "",
max: Double? = null,
enabled: Boolean = true,
) {
var value by rememberSaveable { mutableStateOf(defaultValue) }
AmountInput(
modifier = Modifier.fillMaxWidth(),
value = value,
onValueChange = { next, _ -> value = next },
currency = currency,
locale = Locale.forLanguageTag(locale),
max = max,
enabled = enabled,
)
}Disabled
A disabled input with a dimmed amount and keypad.


import { AmountInput } from "@/components/ui/amount-input"
export function AmountInputDisabledExample() {
return (
<AmountInput currency="USD" locale="en-US" defaultValue="42" disabled />
)
}import EsuissUI
import SwiftUI
struct AmountInputDisabledExample: View {
var body: some View {
AmountInputDemo(currency: "USD", locale: "en-US", initial: "42")
.disabled(true)
}
}
struct AmountInputDemo: View {
let currency: String
let locale: String
var max: Double?
@State private var value: String
init(currency: String, locale: String, initial: String = "", max: Double? = nil) {
self.currency = currency
self.locale = locale
self.max = max
_value = State(initialValue: initial)
}
var body: some View {
AmountInput(value: $value, currency: currency, max: max)
.environment(\.locale, Locale(identifier: locale))
}
}import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.esuiss.ui.components.AmountInput
import java.util.Locale
@Composable
fun AmountInputDisabledExample() {
AmountInputStory("USD", "en-US", "42", enabled = false)
}
@Composable
fun AmountInputStory(
currency: String,
locale: String,
defaultValue: String = "",
max: Double? = null,
enabled: Boolean = true,
) {
var value by rememberSaveable { mutableStateOf(defaultValue) }
AmountInput(
modifier = Modifier.fillMaxWidth(),
value = value,
onValueChange = { next, _ -> value = next },
currency = currency,
locale = Locale.forLanguageTag(locale),
max = max,
enabled = enabled,
)
}API Reference
AmountInput
React.ComponentProps<typeof View>- About
- Controlled numeric string.
- Type
string- Default
- —
- About
- Initial numeric string when uncontrolled.
- Type
string- Default
- ""
- About
- Called with the new string and its numeric amount.
- Type
(value: string, amount: number) => void- Default
- —
- About
- ISO currency code used for formatting.
- Type
string- Default
- —
- About
- Locale used for formatting and the decimal separator.
- Type
string- Default
- —
- About
- Maximum digits after the decimal separator.
- Type
number- Default
- —
- About
- Largest allowed amount.
- Type
number- Default
- —
- About
- Shown in place of the integer when empty. Defaults to
"0". - Type
string- Default
- "0"
- About
- Dims the amount and disables the keypad.
- Type
boolean- Default
- false
- About
- Accessibility label for the delete key.
- Type
string- Default
- —
- About
- Accessibility label for the decimal key.
- Type
string- Default
- —
- About
- Extra classes for the amount text.
- Type
string- Default
- —
- About
- Extra classes for the keypad.
- Type
string- Default
- —
Accessibility
- The amount text is a polite live region, so changes are announced.
- Its label is the formatted amount, prefixed with
accessibilityLabelwhen given. - Keypad keys announce their digit; the delete key is disabled while the value is empty.
Notes
- The shake animation respects the system Reduce Motion setting.
- Uses tabular numerals so digits do not shift as they change.
- Uses
text-labelandtext-label-tertiarytokens; light and dark follow the system appearance.