x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
<div class="flex flex-col gap-4"> <div> <h3 class="text-primary-md-medium mb-2">Euro (EUR)</h3> <div class="lui-money lui-money--inline relative" data-controller="money-input"> <div data-controller="input" data-input-open-actions-value="false" class="lui-inner-input relative flex gap-2" data-input-original-input-value="99.99" data-input-mode-value="inline" data-input-form-value=""> <div class="w-full flex flex-col"> <span class="lui-input "> <span class="lui-input__addon-left"> <span class="lui-money__currency text-gray-700"> € </span> </span> <input name="price_eur" type="number" value="99.99" placeholder="0.00" class="lui-input__input" mode="inline" contentEditable="true" data-input-target="input" data-action="input->input#onChange change->input#onChange" data-money-input-target="input" min="0" step="0.01" inputmode="decimal" pattern="[0-9]*[.,]?[0-9]*"> <span class="lui-input__spinner"> <i class="fa-regular fa-spinner"></i> </span> </span> </div> <span class="lui-inner-input__actions opacity-0 flex items-center gap-1 h-fit" data-input-target="actions"> <button class="lui-button lui-button--icon-only lui-button--neutral--secondary lui-button--size-tiny w-fit w-fit relative" data-controller="lui--button" data-input-target="cancel" data-action="click->input#handleClose" type="button" disabled="disabled"> <div class="opacity-100 inline-flex" data-lui--button-target="leadingIcon"> <div class="flex items-center justify-center" style="width: 12px; height: 12px;"><i class="lui-button__icon lui-button__icon--tiny fa-regular fa-xmark" data-lui--button-target="leadingIcon"></i></div> </div> <div class="absolute w-full flex items-center justify-center opacity-0" data-lui--button-target="loadingIcon"> <i class="lui-m_icon animate-spin material-symbols-outlined" style="--lui-micon-size: 12px;"> progress_activity </i> </div> </button> <button class="lui-button lui-button--icon-only lui-button--neutral--secondary lui-button--size-tiny w-fit w-fit relative" data-controller="lui--button" data-input-target="submit" data-action="click->input#setLoading" type="submit" disabled="disabled"> <div class="opacity-100 inline-flex" data-lui--button-target="leadingIcon"> <div class="flex items-center justify-center" style="width: 12px; height: 12px;"><i class="lui-button__icon lui-button__icon--tiny fa-regular fa-check" data-lui--button-target="leadingIcon"></i></div> </div> <div class="absolute w-full flex items-center justify-center opacity-0" data-lui--button-target="loadingIcon"> <i class="lui-m_icon animate-spin material-symbols-outlined" style="--lui-micon-size: 12px;"> progress_activity </i> </div> </button> </span> </div> </div> </div> <div> <h3 class="text-primary-md-medium mb-2">US Dollar (USD)</h3> <div class="lui-money lui-money--inline relative" data-controller="money-input"> <div data-controller="input" data-input-open-actions-value="false" class="lui-inner-input relative flex gap-2" data-input-original-input-value="149.99" data-input-mode-value="inline" data-input-form-value=""> <div class="w-full flex flex-col"> <span class="lui-input "> <span class="lui-input__addon-left"> <span class="lui-money__currency text-gray-700"> $ </span> </span> <input name="price_usd" type="number" value="149.99" placeholder="0.00" class="lui-input__input" mode="inline" contentEditable="true" data-input-target="input" data-action="input->input#onChange change->input#onChange" data-money-input-target="input" min="0" step="0.01" inputmode="decimal" pattern="[0-9]*[.,]?[0-9]*"> <span class="lui-input__spinner"> <i class="fa-regular fa-spinner"></i> </span> </span> </div> <span class="lui-inner-input__actions opacity-0 flex items-center gap-1 h-fit" data-input-target="actions"> <button class="lui-button lui-button--icon-only lui-button--neutral--secondary lui-button--size-tiny w-fit w-fit relative" data-controller="lui--button" data-input-target="cancel" data-action="click->input#handleClose" type="button" disabled="disabled"> <div class="opacity-100 inline-flex" data-lui--button-target="leadingIcon"> <div class="flex items-center justify-center" style="width: 12px; height: 12px;"><i class="lui-button__icon lui-button__icon--tiny fa-regular fa-xmark" data-lui--button-target="leadingIcon"></i></div> </div> <div class="absolute w-full flex items-center justify-center opacity-0" data-lui--button-target="loadingIcon"> <i class="lui-m_icon animate-spin material-symbols-outlined" style="--lui-micon-size: 12px;"> progress_activity </i> </div> </button> <button class="lui-button lui-button--icon-only lui-button--neutral--secondary lui-button--size-tiny w-fit w-fit relative" data-controller="lui--button" data-input-target="submit" data-action="click->input#setLoading" type="submit" disabled="disabled"> <div class="opacity-100 inline-flex" data-lui--button-target="leadingIcon"> <div class="flex items-center justify-center" style="width: 12px; height: 12px;"><i class="lui-button__icon lui-button__icon--tiny fa-regular fa-check" data-lui--button-target="leadingIcon"></i></div> </div> <div class="absolute w-full flex items-center justify-center opacity-0" data-lui--button-target="loadingIcon"> <i class="lui-m_icon animate-spin material-symbols-outlined" style="--lui-micon-size: 12px;"> progress_activity </i> </div> </button> </span> </div> </div> </div> <div> <h3 class="text-primary-md-medium mb-2">British Pound (GBP)</h3> <div class="lui-money lui-money--inline relative" data-controller="money-input"> <div data-controller="input" data-input-open-actions-value="false" class="lui-inner-input relative flex gap-2" data-input-original-input-value="79.99" data-input-mode-value="inline" data-input-form-value=""> <div class="w-full flex flex-col"> <span class="lui-input "> <span class="lui-input__addon-left"> <span class="lui-money__currency text-gray-700"> £ </span> </span> <input name="price_gbp" type="number" value="79.99" placeholder="0.00" class="lui-input__input" mode="inline" contentEditable="true" data-input-target="input" data-action="input->input#onChange change->input#onChange" data-money-input-target="input" min="0" step="0.01" inputmode="decimal" pattern="[0-9]*[.,]?[0-9]*"> <span class="lui-input__spinner"> <i class="fa-regular fa-spinner"></i> </span> </span> </div> <span class="lui-inner-input__actions opacity-0 flex items-center gap-1 h-fit" data-input-target="actions"> <button class="lui-button lui-button--icon-only lui-button--neutral--secondary lui-button--size-tiny w-fit w-fit relative" data-controller="lui--button" data-input-target="cancel" data-action="click->input#handleClose" type="button" disabled="disabled"> <div class="opacity-100 inline-flex" data-lui--button-target="leadingIcon"> <div class="flex items-center justify-center" style="width: 12px; height: 12px;"><i class="lui-button__icon lui-button__icon--tiny fa-regular fa-xmark" data-lui--button-target="leadingIcon"></i></div> </div> <div class="absolute w-full flex items-center justify-center opacity-0" data-lui--button-target="loadingIcon"> <i class="lui-m_icon animate-spin material-symbols-outlined" style="--lui-micon-size: 12px;"> progress_activity </i> </div> </button> <button class="lui-button lui-button--icon-only lui-button--neutral--secondary lui-button--size-tiny w-fit w-fit relative" data-controller="lui--button" data-input-target="submit" data-action="click->input#setLoading" type="submit" disabled="disabled"> <div class="opacity-100 inline-flex" data-lui--button-target="leadingIcon"> <div class="flex items-center justify-center" style="width: 12px; height: 12px;"><i class="lui-button__icon lui-button__icon--tiny fa-regular fa-check" data-lui--button-target="leadingIcon"></i></div> </div> <div class="absolute w-full flex items-center justify-center opacity-0" data-lui--button-target="loadingIcon"> <i class="lui-m_icon animate-spin material-symbols-outlined" style="--lui-micon-size: 12px;"> progress_activity </i> </div> </button> </span> </div> </div> </div> <div> <h3 class="text-primary-md-medium mb-2">Japanese Yen (JPY)</h3> <div class="lui-money lui-money--inline relative" data-controller="money-input"> <div data-controller="input" data-input-open-actions-value="false" class="lui-inner-input relative flex gap-2" data-input-original-input-value="9999" data-input-mode-value="inline" data-input-form-value=""> <div class="w-full flex flex-col"> <span class="lui-input "> <span class="lui-input__addon-left"> <span class="lui-money__currency text-gray-700"> ¥ </span> </span> <input name="price_jpy" type="number" value="9999" placeholder="0" class="lui-input__input" mode="inline" contentEditable="true" data-input-target="input" data-action="input->input#onChange change->input#onChange" data-money-input-target="input" min="0" step="0.01" inputmode="decimal" pattern="[0-9]*[.,]?[0-9]*"> <span class="lui-input__spinner"> <i class="fa-regular fa-spinner"></i> </span> </span> </div> <span class="lui-inner-input__actions opacity-0 flex items-center gap-1 h-fit" data-input-target="actions"> <button class="lui-button lui-button--icon-only lui-button--neutral--secondary lui-button--size-tiny w-fit w-fit relative" data-controller="lui--button" data-input-target="cancel" data-action="click->input#handleClose" type="button" disabled="disabled"> <div class="opacity-100 inline-flex" data-lui--button-target="leadingIcon"> <div class="flex items-center justify-center" style="width: 12px; height: 12px;"><i class="lui-button__icon lui-button__icon--tiny fa-regular fa-xmark" data-lui--button-target="leadingIcon"></i></div> </div> <div class="absolute w-full flex items-center justify-center opacity-0" data-lui--button-target="loadingIcon"> <i class="lui-m_icon animate-spin material-symbols-outlined" style="--lui-micon-size: 12px;"> progress_activity </i> </div> </button> <button class="lui-button lui-button--icon-only lui-button--neutral--secondary lui-button--size-tiny w-fit w-fit relative" data-controller="lui--button" data-input-target="submit" data-action="click->input#setLoading" type="submit" disabled="disabled"> <div class="opacity-100 inline-flex" data-lui--button-target="leadingIcon"> <div class="flex items-center justify-center" style="width: 12px; height: 12px;"><i class="lui-button__icon lui-button__icon--tiny fa-regular fa-check" data-lui--button-target="leadingIcon"></i></div> </div> <div class="absolute w-full flex items-center justify-center opacity-0" data-lui--button-target="loadingIcon"> <i class="lui-m_icon animate-spin material-symbols-outlined" style="--lui-micon-size: 12px;"> progress_activity </i> </div> </button> </span> </div> </div> </div> <div> <h3 class="text-primary-md-medium mb-2">Swiss Franc (CHF)</h3> <div class="lui-money lui-money--inline relative" data-controller="money-input"> <div data-controller="input" data-input-open-actions-value="false" class="lui-inner-input relative flex gap-2" data-input-original-input-value="89.99" data-input-mode-value="inline" data-input-form-value=""> <div class="w-full flex flex-col"> <span class="lui-input "> <span class="lui-input__addon-left"> <span class="lui-money__currency text-gray-700"> CHF </span> </span> <input name="price_chf" type="number" value="89.99" placeholder="0.00" class="lui-input__input" mode="inline" contentEditable="true" data-input-target="input" data-action="input->input#onChange change->input#onChange" data-money-input-target="input" min="0" step="0.01" inputmode="decimal" pattern="[0-9]*[.,]?[0-9]*"> <span class="lui-input__spinner"> <i class="fa-regular fa-spinner"></i> </span> </span> </div> <span class="lui-inner-input__actions opacity-0 flex items-center gap-1 h-fit" data-input-target="actions"> <button class="lui-button lui-button--icon-only lui-button--neutral--secondary lui-button--size-tiny w-fit w-fit relative" data-controller="lui--button" data-input-target="cancel" data-action="click->input#handleClose" type="button" disabled="disabled"> <div class="opacity-100 inline-flex" data-lui--button-target="leadingIcon"> <div class="flex items-center justify-center" style="width: 12px; height: 12px;"><i class="lui-button__icon lui-button__icon--tiny fa-regular fa-xmark" data-lui--button-target="leadingIcon"></i></div> </div> <div class="absolute w-full flex items-center justify-center opacity-0" data-lui--button-target="loadingIcon"> <i class="lui-m_icon animate-spin material-symbols-outlined" style="--lui-micon-size: 12px;"> progress_activity </i> </div> </button> <button class="lui-button lui-button--icon-only lui-button--neutral--secondary lui-button--size-tiny w-fit w-fit relative" data-controller="lui--button" data-input-target="submit" data-action="click->input#setLoading" type="submit" disabled="disabled"> <div class="opacity-100 inline-flex" data-lui--button-target="leadingIcon"> <div class="flex items-center justify-center" style="width: 12px; height: 12px;"><i class="lui-button__icon lui-button__icon--tiny fa-regular fa-check" data-lui--button-target="leadingIcon"></i></div> </div> <div class="absolute w-full flex items-center justify-center opacity-0" data-lui--button-target="loadingIcon"> <i class="lui-m_icon animate-spin material-symbols-outlined" style="--lui-micon-size: 12px;"> progress_activity </i> </div> </button> </span> </div> </div> </div></div>Inputs::Money
Description
Related components
| Used Components | Components where is Used |
|---|---|
| Label |
Usage rules
- ✅ Do
- ❌ Don't
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<div class="flex flex-col gap-4"> <div> <h3 class="text-primary-md-medium mb-2">Euro (EUR)</h3> <%= render LooposUi::Inputs::Money.new( name: "price_eur", value: "99.99", currency: "EUR", placeholder: "0.00" ) %> </div> <div> <h3 class="text-primary-md-medium mb-2">US Dollar (USD)</h3> <%= render LooposUi::Inputs::Money.new( name: "price_usd", value: "149.99", currency: "USD", placeholder: "0.00" ) %> </div> <div> <h3 class="text-primary-md-medium mb-2">British Pound (GBP)</h3> <%= render LooposUi::Inputs::Money.new( name: "price_gbp", value: "79.99", currency: "GBP", placeholder: "0.00" ) %> </div> <div> <h3 class="text-primary-md-medium mb-2">Japanese Yen (JPY)</h3> <%= render LooposUi::Inputs::Money.new( name: "price_jpy", value: "9999", currency: "JPY", placeholder: "0" ) %> </div> <div> <h3 class="text-primary-md-medium mb-2">Swiss Franc (CHF)</h3> <%= render LooposUi::Inputs::Money.new( name: "price_chf", value: "89.99", currency: "CHF", placeholder: "0.00" ) %> </div></div>No notes provided.
No params configured.
Description
The Money input component is a specialized number input for monetary values. It displays a currency symbol on the left side of the input and only accepts numeric values (with decimals).
Arguments
| Property | Type | Default | Description |
|---|---|---|---|
currency |
String | "EUR" |
The currency code (EUR, USD, GBP, JPY, CHF). Displays the appropriate symbol (€, $, £, ¥, CHF) |
min |
Number | 0 |
Minimum allowed value |
max |
Number | nil |
Maximum allowed value |
step |
Number | 0.01 |
Step increment for the input |
model |
Object | nil |
ActiveRecord model instance (alternative to manual name/value) |
attribute |
Symbol | nil |
Model attribute name (used with model) |
name |
String | nil |
Input name attribute (required if not using model) |
value |
String | nil |
Initial value |
placeholder |
String | nil |
Placeholder text |
error |
String | nil |
Error message to display |
help |
String | nil |
Help text to display |
mode |
Symbol | :inline |
Input mode (:inline, :form, :autosubmit) |
readonly |
Boolean | false |
Whether the input is read-only |
Usage
Basic Usage
<%= render LooposUi::Inputs::Money.new( name: "price", value: "99.99", currency: "EUR") %>With Different Currencies
<!-- Euro --><%= render LooposUi::Inputs::Money.new( name: "price_eur", value: "99.99", currency: "EUR") %><!-- US Dollar --><%= render LooposUi::Inputs::Money.new( name: "price_usd", value: "99.99", currency: "USD") %><!-- British Pound --><%= render LooposUi::Inputs::Money.new( name: "price_gbp", value: "99.99", currency: "GBP") %>With Model
<%= render LooposUi::Inputs::Money.new( model: @product, attribute: :price, currency: "EUR") %>With Validation
<%= render LooposUi::Inputs::Money.new( name: "price", value: "99.99", currency: "EUR", min: 0, max: 9999.99, error: "Price must be between 0 and 9999.99") %>In Form Mode
<%= render LooposUi::Inputs::Money.new( name: "price", value: "99.99", currency: "USD", mode: :form, placeholder: "0.00") %>Supported Currencies
The component supports the following currencies with their symbols:
- EUR (Euro): €
- USD (US Dollar): $
- GBP (British Pound): £
- JPY (Japanese Yen): ¥
- CHF (Swiss Franc): CHF
For other currencies, the component will display the currency code itself.
Features
- ✅ Currency symbol displayed on the left
- ✅ Only accepts numeric input (with decimals)
- ✅ Real-time input validation - invalid characters are prevented from being typed
- ✅ No spinner arrows on the input (cleaner UI)
- ✅ Supports model binding with automatic error display
- ✅ Supports min/max validation
- ✅ Supports different input modes (inline, form, autosubmit)
- ✅ Smart paste handling - automatically cleans pasted content
- ✅ Responsive and accessible
Input Validation
The Money component uses a JavaScript Stimulus controller (money-input) that prevents invalid characters from being entered in real-time. This provides a better user experience compared to browser validation that only checks after input.
How It Works
The controller uses three event listeners:
keydownevent - Prevents invalid keys from being pressedbeforeinputevent - Validates characters before they're inserted (second line of defense)pasteevent - Validates and cleans pasted content
Allowed Input
✅ Allowed:
- Digits:
0-9 - Decimal separator:
.or,(only one allowed) - Control keys: Backspace, Delete, Arrow keys, Tab, Home, End
- System shortcuts: Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+X, Ctrl+Z (and Cmd on Mac)
❌ Blocked:
- Letters:
a-z,A-Z - Special characters:
!@#$%^&*()etc. - Currency symbols:
€$£¥(displayed separately as addon) - Multiple decimal separators
- Any other non-numeric characters
Paste Behavior
When pasting content, the controller:
- Extracts only valid characters (digits and one decimal separator)
- Removes all invalid characters automatically
- Inserts the cleaned text at cursor position
Example:
- User pastes:
"abc€123.45xyz" - Controller inserts:
"123.45"
Notes
- The input uses
type="number"withinputmode="decimal"for better mobile keyboard support - The default step is
0.01(2 decimal places), suitable for most currencies - For currencies like JPY that don't use decimal places, you can set
step: 1 - The component inherits all the features from the base Input component (error handling, help text, etc.)
- Invalid keys simply don't work - they won't be entered into the input field