al-design-system 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,7 +10,7 @@ An open-source design system, from Figma to code. Plain CSS, a little vanilla Ja
10
10
 
11
11
  ## What it is
12
12
 
13
- - **Foundation + 25 components**, designed in Figma and coded from the same tokens.
13
+ - **Foundation + 27 components**, designed in Figma and coded from the same tokens.
14
14
  - **Light and dark themes** built in. It follows the operating system by default, and you can force one.
15
15
  - **Accessibility is checked by the build.** Every rendered combination (variant × state × theme) is measured for contrast against the background it actually sits on, and each component's markup contract is verified. If something fails, the build stops.
16
16
  - **Nothing is typed by hand.** Tokens, CSS variables, `tokens.json` and the documentation tables are all generated from one source.
@@ -32,6 +32,7 @@ An open-source design system, from Figma to code. Plain CSS, a little vanilla Ja
32
32
  | | Input | | [guidelines](src/components/input/guidelines.md) |
33
33
  | | Textarea | | [guidelines](src/components/textarea/guidelines.md) |
34
34
  | | Password | ✓ | [guidelines](src/components/password/guidelines.md) |
35
+ | | Number field | ✓ | [guidelines](src/components/number-field/guidelines.md) |
35
36
  | Structure and overlays | Divider | | [guidelines](src/components/divider/guidelines.md) |
36
37
  | | Card | | [guidelines](src/components/card/guidelines.md) |
37
38
  | | Tab | ✓ | [guidelines](src/components/tab/guidelines.md) |
@@ -40,6 +41,7 @@ An open-source design system, from Figma to code. Plain CSS, a little vanilla Ja
40
41
  | | Drawer | ✓ | [guidelines](src/components/drawer/guidelines.md) |
41
42
  | Navigation | Sidebar | ✓ | [guidelines](src/components/sidebar/guidelines.md) |
42
43
  | | Breadcrumb | ✓ | [guidelines](src/components/breadcrumb/guidelines.md) |
44
+ | | Pagination | | [guidelines](src/components/pagination/guidelines.md) |
43
45
  | Feedback | Tooltip | ✓ | [guidelines](src/components/tooltip/guidelines.md) |
44
46
  | | Toast | ✓ | [guidelines](src/components/toast/guidelines.md) |
45
47
  | | Alert | ✓ | [guidelines](src/components/alert/guidelines.md) |
@@ -56,7 +58,7 @@ npm install al-design-system
56
58
  Or straight from a CDN, pinned to a version:
57
59
 
58
60
  ```html
59
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/al-design-system@1.2.0/dist/al.css">
61
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/al-design-system@1.4.0/dist/al.css">
60
62
  ```
61
63
 
62
64
  Each [GitHub Release](https://github.com/guilhermedworakowski/al-design-system/releases) also carries the `dist/` folder as a zip.
@@ -65,10 +67,10 @@ Each [GitHub Release](https://github.com/guilhermedworakowski/al-design-system/r
65
67
 
66
68
  | File | What it is |
67
69
  |---|---|
68
- | `al.css` | Foundation + all 25 components, in one file |
70
+ | `al.css` | Foundation + all 27 components, in one file |
69
71
  | `foundation.css` | Only the Foundation: color, typography, spacing, radius, icon size, elevation, focus ring and motion |
70
72
  | `components/<name>.css` | One component (its tokens + its CSS) |
71
- | `components/<name>.js` | The behavior, for the 9 components that have a script |
73
+ | `components/<name>.js` | The behavior, for the 10 components that have a script |
72
74
  | `icons/*.svg`, `icons/icons.json` | The 70 icons |
73
75
  | `tokens.json` | The Foundation tokens, to read from code |
74
76
 
@@ -99,7 +101,7 @@ The exact markup of each component (which element, which attributes) is describe
99
101
 
100
102
  ### Scripts
101
103
 
102
- Password, Tab, Modal, Drawer, Sidebar, Breadcrumb, Tooltip, Toast and Alert need their script, loaded once per page. Each one wires up the components already on the page by itself:
104
+ Password, Number field, Tab, Modal, Drawer, Sidebar, Breadcrumb, Tooltip, Toast and Alert need their script, loaded once per page. Each one wires up the components already on the page by itself:
103
105
 
104
106
  ```html
105
107
  <script src="node_modules/al-design-system/dist/components/modal.js" defer></script>
package/README.pt-BR.md CHANGED
@@ -10,7 +10,7 @@ Design system open source, do Figma ao código. CSS puro, um pouco de JavaScript
10
10
 
11
11
  ## O que é
12
12
 
13
- - **Foundation + 25 componentes**, desenhados no Figma e codados a partir dos mesmos tokens.
13
+ - **Foundation + 27 componentes**, desenhados no Figma e codados a partir dos mesmos tokens.
14
14
  - **Temas claro e escuro** já incluídos. Por padrão segue o sistema operacional, e dá para forçar um.
15
15
  - **A acessibilidade é conferida no build.** Cada combinação renderizada (variante × estado × tema) tem o contraste medido contra o fundo onde ela realmente aparece, e o contrato de marcação de cada componente é verificado. Se algo reprova, o build para.
16
16
  - **Nada é digitado à mão.** Tokens, variáveis CSS, `tokens.json` e as tabelas da documentação saem todos de uma fonte só.
@@ -32,6 +32,7 @@ Design system open source, do Figma ao código. CSS puro, um pouco de JavaScript
32
32
  | | Input | | [diretrizes](src/components/input/guidelines.md) |
33
33
  | | Textarea | | [diretrizes](src/components/textarea/guidelines.md) |
34
34
  | | Password | ✓ | [diretrizes](src/components/password/guidelines.md) |
35
+ | | Number field | ✓ | [diretrizes](src/components/number-field/guidelines.md) |
35
36
  | Estrutura e sobreposição | Divider | | [diretrizes](src/components/divider/guidelines.md) |
36
37
  | | Card | | [diretrizes](src/components/card/guidelines.md) |
37
38
  | | Tab | ✓ | [diretrizes](src/components/tab/guidelines.md) |
@@ -40,6 +41,7 @@ Design system open source, do Figma ao código. CSS puro, um pouco de JavaScript
40
41
  | | Drawer | ✓ | [diretrizes](src/components/drawer/guidelines.md) |
41
42
  | Navegação | Sidebar | ✓ | [diretrizes](src/components/sidebar/guidelines.md) |
42
43
  | | Breadcrumb | ✓ | [diretrizes](src/components/breadcrumb/guidelines.md) |
44
+ | | Pagination | | [diretrizes](src/components/pagination/guidelines.md) |
43
45
  | Feedback | Tooltip | ✓ | [diretrizes](src/components/tooltip/guidelines.md) |
44
46
  | | Toast | ✓ | [diretrizes](src/components/toast/guidelines.md) |
45
47
  | | Alert | ✓ | [diretrizes](src/components/alert/guidelines.md) |
@@ -56,7 +58,7 @@ npm install al-design-system
56
58
  Ou direto de uma CDN, fixado numa versão:
57
59
 
58
60
  ```html
59
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/al-design-system@1.2.0/dist/al.css">
61
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/al-design-system@1.4.0/dist/al.css">
60
62
  ```
61
63
 
62
64
  Cada [Release no GitHub](https://github.com/guilhermedworakowski/al-design-system/releases) também traz a pasta `dist/` num zip.
@@ -65,10 +67,10 @@ Cada [Release no GitHub](https://github.com/guilhermedworakowski/al-design-syste
65
67
 
66
68
  | Arquivo | O que é |
67
69
  |---|---|
68
- | `al.css` | Foundation + os 25 componentes, num arquivo só |
70
+ | `al.css` | Foundation + os 27 componentes, num arquivo só |
69
71
  | `foundation.css` | Só a Foundation: cor, tipografia, espaçamento, radius, tamanho de ícone, elevação, anel de foco e motion |
70
72
  | `components/<nome>.css` | Um componente (os tokens + o CSS dele) |
71
- | `components/<nome>.js` | O comportamento, para os 9 componentes que têm script |
73
+ | `components/<nome>.js` | O comportamento, para os 10 componentes que têm script |
72
74
  | `icons/*.svg`, `icons/icons.json` | Os 70 ícones |
73
75
  | `tokens.json` | Os tokens da Foundation, para ler por código |
74
76
 
@@ -99,7 +101,7 @@ A marcação exata de cada componente (qual elemento, quais atributos) está des
99
101
 
100
102
  ### Scripts
101
103
 
102
- Password, Tab, Modal, Drawer, Sidebar, Breadcrumb, Tooltip, Toast e Alert precisam do script, carregado uma vez por página. Cada um liga sozinho os componentes que já estão na página:
104
+ Password, Number field, Tab, Modal, Drawer, Sidebar, Breadcrumb, Tooltip, Toast e Alert precisam do script, carregado uma vez por página. Cada um liga sozinho os componentes que já estão na página:
103
105
 
104
106
  ```html
105
107
  <script src="node_modules/al-design-system/dist/components/modal.js" defer></script>
package/dist/al.css CHANGED
@@ -1,8 +1,8 @@
1
- /* AL Design System 1.2.0 - Foundation + 25 components.
1
+ /* AL Design System 1.4.0 - Foundation + 27 components.
2
2
  * MIT. https://github.com/guilhermedworakowski/al-design-system
3
3
  */
4
4
  /* AL Design System - Foundation
5
- * v1.2.0 - MIT
5
+ * v1.4.0 - MIT
6
6
  * GENERATED by src/foundation/css.py. Do not edit by hand: run the script.
7
7
  */
8
8
 
@@ -7046,3 +7046,564 @@
7046
7046
  @media (prefers-reduced-motion: reduce) {
7047
7047
  .al-progress__bar::-webkit-progress-value { transition: none; }
7048
7048
  }
7049
+
7050
+ /* AL Design System - Progress tokens
7051
+ * GENERATED by src/components/pagination/tokens.py. Do not edit by hand.
7052
+ *
7053
+ * Every color is a semantic the theme switches on :root.
7054
+ */
7055
+
7056
+ :root {
7057
+ /* color */
7058
+ --al-pagination-item-label: var(--al-text-primary);
7059
+ --al-pagination-item-bg-hover: var(--al-bg-hover);
7060
+ --al-pagination-item-bg-active: var(--al-bg-active);
7061
+ --al-pagination-ellipsis: var(--al-text-secondary);
7062
+ --al-pagination-current-bg: var(--al-bg-brand-subtle);
7063
+ --al-pagination-current-label: var(--al-text-brand);
7064
+ --al-pagination-current-bg-hover: var(--al-bg-brand-hover);
7065
+ --al-pagination-current-bg-active: var(--al-bg-brand-active);
7066
+ --al-pagination-current-label-hover: var(--al-text-on-brand);
7067
+ --al-pagination-range-text: var(--al-text-secondary);
7068
+
7069
+ /* geometry */
7070
+ --al-pagination-item-padding: var(--al-space-8);
7071
+ --al-pagination-item-radius: var(--al-radius-full);
7072
+ --al-pagination-gap: var(--al-space-4);
7073
+ --al-pagination-range-gap: var(--al-space-16);
7074
+ --al-pagination-rows-gap: var(--al-space-8);
7075
+
7076
+ /* type - page numbers like the Button sm, range like body-sm */
7077
+ --al-pagination-font-size: var(--al-font-size-sm);
7078
+ --al-pagination-line-height: var(--al-line-height-sm);
7079
+ --al-pagination-font-weight: var(--al-font-weight-medium);
7080
+ --al-pagination-range-font-weight: var(--al-font-weight-regular);
7081
+ }
7082
+
7083
+ /* AL Design System - Pagination
7084
+ *
7085
+ * Four types, one block. Previous, Next and Load more are Buttons (Ghost sm,
7086
+ * Secondary sm) and rows per page is the Select: this file only lays them out
7087
+ * and draws what is the Pagination's own - the page items and the range text.
7088
+ *
7089
+ * .al-pagination Numbered - previous, pages, next
7090
+ * .al-pagination--simple Simple - previous and next only
7091
+ * .al-pagination--load-more Load more - one Secondary Button
7092
+ * .al-pagination--range Range - "Showing 41-60 of 312" + rows
7093
+ *
7094
+ * Markup (usage rules 20-22): a <nav aria-label="Pagination"> with the pages
7095
+ * in a <ul>. A page is an <a> when it changes the URL and a <button> when it
7096
+ * swaps content in place. The current page carries aria-current="page" - that
7097
+ * is what styles it, so the look can never disagree with what is announced.
7098
+ * The ellipsis is aria-hidden: it is not a control (rule 10).
7099
+ *
7100
+ * Disabled previous/next is the Button's own disabled state (rule 16), not a
7101
+ * Pagination style.
7102
+ *
7103
+ * NEVER ON SURFACE-RAISED (rule 8): the hovers use bg-hover/bg-active, which
7104
+ * vanish on the raised surface.
7105
+ *
7106
+ * NO HEIGHT: 36 = padding 8 + line 20 + padding 8. min-inline-size uses the
7107
+ * same sum so a one-digit page stays a circle and "16" grows into a pill.
7108
+ */
7109
+
7110
+ /* ONE LINE (decided with Gui on 2026-10-21): wrapping split Previous, the
7111
+ pages and Next into three rows. The row never wraps; when it does not fit,
7112
+ the CONTAINER scrolls sideways (overflow-x: auto, with room for the focus
7113
+ ring). Range is the exception: text and rows may wrap. */
7114
+ .al-pagination {
7115
+ display: flex;
7116
+ flex-wrap: nowrap;
7117
+ align-items: center;
7118
+ gap: var(--al-pagination-gap);
7119
+ inline-size: max-content;
7120
+ }
7121
+
7122
+ .al-pagination--range {
7123
+ flex-wrap: wrap;
7124
+ inline-size: auto;
7125
+ }
7126
+
7127
+ .al-pagination__pages {
7128
+ display: flex;
7129
+ align-items: center;
7130
+ gap: var(--al-pagination-gap);
7131
+ margin: 0;
7132
+ padding: 0;
7133
+ list-style: none;
7134
+ }
7135
+
7136
+ /* ------------------------------------------------------------- page item */
7137
+ .al-pagination__item {
7138
+ display: inline-flex;
7139
+ align-items: center;
7140
+ justify-content: center;
7141
+ box-sizing: border-box;
7142
+ min-inline-size: calc(var(--al-pagination-item-padding) * 2 + var(--al-pagination-line-height));
7143
+ margin: 0;
7144
+ padding: var(--al-pagination-item-padding);
7145
+ border: 0;
7146
+ border-radius: var(--al-pagination-item-radius);
7147
+ background-color: transparent;
7148
+ font-family: var(--al-font-sans);
7149
+ font-size: var(--al-pagination-font-size);
7150
+ line-height: var(--al-pagination-line-height);
7151
+ font-weight: var(--al-pagination-font-weight);
7152
+ font-variant-numeric: tabular-nums;
7153
+ color: var(--al-pagination-item-label);
7154
+ text-decoration: none;
7155
+ cursor: pointer;
7156
+ -webkit-tap-highlight-color: transparent;
7157
+ transition: background-color var(--al-motion-duration-feedback) var(--al-motion-easing-exit), color var(--al-motion-duration-feedback) var(--al-motion-easing-exit), box-shadow var(--al-motion-duration-feedback) var(--al-motion-easing-exit);
7158
+ }
7159
+
7160
+ .al-pagination__item:hover {
7161
+ background-color: var(--al-pagination-item-bg-hover);
7162
+ }
7163
+
7164
+ .al-pagination__item:active {
7165
+ background-color: var(--al-pagination-item-bg-active);
7166
+ }
7167
+
7168
+ /* focus is rest plus the ring, keyboard only */
7169
+ .al-pagination__item:focus-visible {
7170
+ outline: none;
7171
+ box-shadow: var(--al-focus-ring-default);
7172
+ }
7173
+
7174
+ /* current: tonal at rest and focus, brand on hover and pressed (rule 19) */
7175
+ .al-pagination__item[aria-current="page"] {
7176
+ background-color: var(--al-pagination-current-bg);
7177
+ color: var(--al-pagination-current-label);
7178
+ }
7179
+
7180
+ .al-pagination__item[aria-current="page"]:hover {
7181
+ background-color: var(--al-pagination-current-bg-hover);
7182
+ color: var(--al-pagination-current-label-hover);
7183
+ }
7184
+
7185
+ .al-pagination__item[aria-current="page"]:active {
7186
+ background-color: var(--al-pagination-current-bg-active);
7187
+ color: var(--al-pagination-current-label-hover);
7188
+ }
7189
+
7190
+ /* ENTER - destination states take the enter curve, as in the Tab */
7191
+ .al-pagination__item:hover,
7192
+ .al-pagination__item:active,
7193
+ .al-pagination__item:focus-visible {
7194
+ transition-timing-function: var(--al-motion-easing-enter);
7195
+ }
7196
+
7197
+ /* ------------------------------------------------------------- ellipsis */
7198
+ .al-pagination__ellipsis {
7199
+ display: inline-flex;
7200
+ align-items: center;
7201
+ justify-content: center;
7202
+ min-inline-size: calc(var(--al-pagination-item-padding) * 2 + var(--al-pagination-line-height));
7203
+ padding: var(--al-pagination-item-padding);
7204
+ box-sizing: border-box;
7205
+ color: var(--al-pagination-ellipsis);
7206
+ }
7207
+
7208
+ /* ---------------------------------------------------------------- range */
7209
+ .al-pagination--range {
7210
+ gap: var(--al-pagination-range-gap);
7211
+ }
7212
+
7213
+ .al-pagination__range,
7214
+ .al-pagination__rows-label {
7215
+ margin: 0;
7216
+ font-family: var(--al-font-sans);
7217
+ font-size: var(--al-pagination-font-size);
7218
+ line-height: var(--al-pagination-line-height);
7219
+ font-weight: var(--al-pagination-range-font-weight);
7220
+ font-variant-numeric: tabular-nums;
7221
+ color: var(--al-pagination-range-text);
7222
+ }
7223
+
7224
+ .al-pagination__rows {
7225
+ display: flex;
7226
+ align-items: center;
7227
+ gap: var(--al-pagination-rows-gap);
7228
+ }
7229
+
7230
+ @media (prefers-reduced-motion: reduce) {
7231
+ .al-pagination__item {
7232
+ transition: none;
7233
+ }
7234
+ }
7235
+
7236
+ @media (forced-colors: active) {
7237
+ .al-pagination__item[aria-current="page"] {
7238
+ outline: var(--al-border-width-1) solid CanvasText;
7239
+ }
7240
+
7241
+ .al-pagination__item:focus-visible {
7242
+ outline: var(--al-border-width-focus) solid Highlight;
7243
+ outline-offset: var(--al-border-focus-offset);
7244
+ }
7245
+ }
7246
+
7247
+ /* AL Design System - Number field tokens
7248
+ * GENERATED by src/components/number-field/tokens.py. Do not edit by hand.
7249
+ *
7250
+ * There is no theme block here: each token points to a semantic, and the
7251
+ * theme switches on :root - the same element where these aliases are declared.
7252
+ *
7253
+ * Field height: 48px, derived, no token.
7254
+ * Stepper target: 24px = icon-size, no token of its own.
7255
+ */
7256
+
7257
+ :root {
7258
+
7259
+ /* background - only disabled and readonly change it */
7260
+ --al-number-field-bg: var(--al-bg-surface-raised);
7261
+ --al-number-field-bg-disabled: var(--al-bg-disabled);
7262
+ --al-number-field-bg-readonly: var(--al-bg-subtle);
7263
+
7264
+ /* border - the axis that carries the state */
7265
+ --al-number-field-border: var(--al-border-default);
7266
+ --al-number-field-border-hover: var(--al-border-strong);
7267
+ --al-number-field-border-focus: var(--al-border-brand);
7268
+ --al-number-field-border-error: var(--al-border-danger);
7269
+ --al-number-field-border-disabled: var(--al-border-default);
7270
+ --al-number-field-border-readonly: var(--al-border-subtle);
7271
+
7272
+ /* value - no placeholder, the field always has a value */
7273
+ --al-number-field-text-value: var(--al-text-primary);
7274
+ --al-number-field-text-disabled: var(--al-text-disabled);
7275
+
7276
+ /* steppers - out of the Tab order, no hover or ring of their own */
7277
+ --al-number-field-stepper-icon: var(--al-text-primary);
7278
+ --al-number-field-stepper-icon-disabled: var(--al-text-disabled);
7279
+
7280
+ /* label, mark and help - they live outside the field, on the canvas */
7281
+ --al-number-field-label: var(--al-text-primary);
7282
+ --al-number-field-label-error: var(--al-text-danger);
7283
+ --al-number-field-label-disabled: var(--al-text-disabled);
7284
+ --al-number-field-optional: var(--al-text-secondary);
7285
+ --al-number-field-help: var(--al-text-secondary);
7286
+ --al-number-field-help-error: var(--al-text-danger);
7287
+
7288
+ /* focus ring - points to the composite shadow, not the raw color */
7289
+ --al-number-field-ring: var(--al-focus-ring-default);
7290
+ --al-number-field-ring-error: var(--al-focus-ring-error);
7291
+
7292
+ /* geometry */
7293
+ --al-number-field-padding-x: var(--al-space-12);
7294
+ --al-number-field-padding-y: var(--al-space-12);
7295
+ --al-number-field-gap: var(--al-space-8);
7296
+ --al-number-field-stepper-gap: var(--al-space-24);
7297
+ --al-number-field-radius: var(--al-radius-lg);
7298
+ --al-number-field-border-width: var(--al-border-width-1);
7299
+ --al-number-field-icon-size: var(--al-icon-size-24);
7300
+
7301
+ /* width of the box - declared literal, the Foundation has no width scale */
7302
+ --al-number-field-width: 140px;
7303
+
7304
+ /* typography - one style becomes four vars */
7305
+ --al-number-field-font-size: var(--al-font-size-md);
7306
+ --al-number-field-line-height: var(--al-line-height-md);
7307
+ --al-number-field-font-weight: 400;
7308
+ --al-number-field-tracking: 0em;
7309
+ --al-number-field-label-font-size: var(--al-font-size-md);
7310
+ --al-number-field-label-line-height: var(--al-line-height-md);
7311
+ --al-number-field-label-font-weight: 400;
7312
+ --al-number-field-label-tracking: 0em;
7313
+ --al-number-field-optional-font-size: var(--al-font-size-sm);
7314
+ --al-number-field-optional-line-height: var(--al-line-height-sm);
7315
+ --al-number-field-optional-font-weight: 400;
7316
+ --al-number-field-optional-tracking: 0em;
7317
+ --al-number-field-help-font-size: var(--al-font-size-xs);
7318
+ --al-number-field-help-line-height: var(--al-line-height-xs);
7319
+ --al-number-field-help-font-weight: 400;
7320
+ --al-number-field-help-tracking: 0.01em;
7321
+ }
7322
+
7323
+ /* AL Design System - Number field
7324
+ *
7325
+ * Depends on: foundation/al-foundation.css
7326
+ * + components/number-field/al-number-field-tokens.css
7327
+ * + components/icon/icon.css
7328
+ * + components/number-field/number-field.js (the steppers)
7329
+ *
7330
+ * A QUANTITY THE PERSON ADJUSTS A LITTLE AT A TIME
7331
+ * A native <input type="number"> between a - and a + button. Numbers that
7332
+ * are not quantities (zip code, phone, card) are the Input with
7333
+ * inputmode="numeric" - rule 2.
7334
+ *
7335
+ * ANATOMY
7336
+ * <div class="al-number-field">
7337
+ * <div class="al-number-field__labelrow">
7338
+ * <label class="al-number-field__label" for="qty">Quantity</label>
7339
+ * <span class="al-number-field__optional">(Optional)</span>
7340
+ * </div>
7341
+ * <div class="al-number-field__control">
7342
+ * <button class="al-number-field__step" type="button" tabindex="-1"
7343
+ * data-step="down" aria-controls="qty"
7344
+ * aria-label="Decrease quantity" hidden>
7345
+ * <svg class="al-icon" aria-hidden="true" focusable="false">minus</svg>
7346
+ * </button>
7347
+ * <input class="al-number-field__field" id="qty" type="number"
7348
+ * min="1" max="10" step="1" value="1"
7349
+ * aria-describedby="qty-help">
7350
+ * <button class="al-number-field__step" type="button" tabindex="-1"
7351
+ * data-step="up" aria-controls="qty"
7352
+ * aria-label="Increase quantity" hidden>
7353
+ * <svg class="al-icon" aria-hidden="true" focusable="false">plus</svg>
7354
+ * </button>
7355
+ * </div>
7356
+ * <p class="al-number-field__help" id="qty-help">From 1 to 10</p>
7357
+ * </div>
7358
+ *
7359
+ * THE STEPPERS ARE OUT OF THE TAB ORDER
7360
+ * One tab stop: the arrow keys and typing change the value (Carbon, rule
7361
+ * 13). So the buttons carry tabindex="-1", have no ring and no hover of
7362
+ * their own (rule 14) - the field's border and ring say where pointer and
7363
+ * focus are. They are born `hidden`: without JavaScript the arrow keys
7364
+ * still work and a button that does nothing never shows (the same as the
7365
+ * Password's eye).
7366
+ *
7367
+ * THE STEPPER AT THE LIMIT IS A DISABLED <button>
7368
+ * At the minimum the - is `disabled`, at the maximum the + (rule 10). The
7369
+ * JS sets the attribute; the CSS only reads `:disabled`. There is no Figma
7370
+ * variant for it.
7371
+ *
7372
+ * THE BOX IS `__control`, NOT THE <input>
7373
+ * The same as the Input: border, background, radius and ring live on
7374
+ * `__control` and the state is read through `:has()`. Without `:has()` the
7375
+ * native outline stays (it is only removed inside @supports).
7376
+ *
7377
+ * THE STATE IS DRIVEN BY THE MARKUP
7378
+ * Error reads `aria-invalid="true"`; disabled `:disabled`; read-only
7379
+ * `[readonly]` - never `:read-only`, which also matches disabled. The same
7380
+ * precedence by variable as the Input: error, read-only, disabled in file
7381
+ * order, disabled wins over all.
7382
+ *
7383
+ * READ-ONLY HAS NO STEPPERS (rule 17)
7384
+ * Nothing can change, so they are hidden and the value stays centered.
7385
+ *
7386
+ * THE BROWSER'S OWN SPINNERS ARE HIDDEN
7387
+ * WebKit draws arrows inside type=number and Firefox draws its own. With
7388
+ * AL's buttons there would be two pairs.
7389
+ *
7390
+ * HEIGHT AND WIDTH
7391
+ * 48 = padding-y x2 + line height, border subtracted from the padding.
7392
+ * The box is fixed at `--al-number-field-width` (140), decided on
7393
+ * 2026-10-21. Label and help text are not limited by it - they may be
7394
+ * wider than the box.
7395
+ *
7396
+ * HANDWRITTEN. The tokens it consumes are generated by tokens.py.
7397
+ */
7398
+
7399
+ /* ---------------------------------------------------------------- root */
7400
+ .al-number-field {
7401
+ display: flex;
7402
+ flex-direction: column;
7403
+ gap: var(--al-number-field-gap);
7404
+ }
7405
+
7406
+ /* ------------------------------------------------------------ label */
7407
+ .al-number-field__labelrow {
7408
+ display: flex;
7409
+ align-items: baseline;
7410
+ gap: var(--al-number-field-gap);
7411
+ }
7412
+
7413
+ .al-number-field__label {
7414
+ color: var(--al-number-field-label);
7415
+ font-family: var(--al-font-sans);
7416
+ font-size: var(--al-number-field-label-font-size);
7417
+ line-height: var(--al-number-field-label-line-height);
7418
+ font-weight: var(--al-number-field-label-font-weight);
7419
+ letter-spacing: var(--al-number-field-label-tracking);
7420
+ }
7421
+
7422
+ .al-number-field__optional {
7423
+ color: var(--al-number-field-optional);
7424
+ font-family: var(--al-font-sans);
7425
+ font-size: var(--al-number-field-optional-font-size);
7426
+ line-height: var(--al-number-field-optional-line-height);
7427
+ font-weight: var(--al-number-field-optional-font-weight);
7428
+ letter-spacing: var(--al-number-field-optional-tracking);
7429
+ }
7430
+
7431
+ /* --------------------------------------------------------------- box */
7432
+ .al-number-field__control {
7433
+ --_border-state: var(--al-number-field-border);
7434
+
7435
+ box-sizing: border-box;
7436
+ display: flex;
7437
+ align-items: center;
7438
+ gap: var(--al-number-field-stepper-gap);
7439
+ width: var(--al-number-field-width);
7440
+
7441
+ padding: calc(var(--al-number-field-padding-y) - var(--al-number-field-border-width))
7442
+ calc(var(--al-number-field-padding-x) - var(--al-number-field-border-width));
7443
+
7444
+ border: var(--al-number-field-border-width) solid
7445
+ var(--_border-force, var(--_border-state));
7446
+ border-radius: var(--al-number-field-radius);
7447
+ background-color: var(--_bg-force, var(--al-number-field-bg));
7448
+ cursor: text;
7449
+
7450
+ transition:
7451
+ border-color var(--al-motion-duration-feedback) var(--al-motion-easing-exit),
7452
+ box-shadow var(--al-motion-duration-feedback) var(--al-motion-easing-exit);
7453
+ }
7454
+
7455
+ /* ------------------------------------------------------------- field */
7456
+ .al-number-field__field {
7457
+ --_text-state: var(--al-number-field-text-value);
7458
+
7459
+ flex: 1 1 0;
7460
+ min-width: 0;
7461
+ width: 100%;
7462
+ margin: 0;
7463
+ padding: 0;
7464
+ border: 0;
7465
+ background: none;
7466
+ color: var(--_text-force, var(--_text-state));
7467
+ text-align: center;
7468
+
7469
+ font-family: var(--al-font-sans);
7470
+ font-size: var(--al-number-field-font-size);
7471
+ line-height: var(--al-number-field-line-height);
7472
+ font-weight: var(--al-number-field-font-weight);
7473
+ letter-spacing: var(--al-number-field-tracking);
7474
+ font-variant-numeric: tabular-nums;
7475
+
7476
+ -moz-appearance: textfield;
7477
+ appearance: textfield;
7478
+ }
7479
+
7480
+ /* the browser's own spinners - see the header */
7481
+ .al-number-field__field::-webkit-inner-spin-button,
7482
+ .al-number-field__field::-webkit-outer-spin-button {
7483
+ -webkit-appearance: none;
7484
+ margin: 0;
7485
+ }
7486
+
7487
+ @supports selector(:has(*)) {
7488
+ .al-number-field__field:focus {
7489
+ outline: none;
7490
+ }
7491
+ }
7492
+
7493
+ /* --------------------------------------------------------- the steppers */
7494
+ .al-number-field__step {
7495
+ display: inline-flex;
7496
+ align-items: center;
7497
+ justify-content: center;
7498
+ flex: none;
7499
+
7500
+ box-sizing: border-box;
7501
+ width: var(--al-number-field-icon-size);
7502
+ height: var(--al-number-field-icon-size);
7503
+ margin: 0;
7504
+ padding: 0;
7505
+ border: 0;
7506
+ background: none;
7507
+ /* the svg inherits from here through currentColor - the Icon contract */
7508
+ color: var(--al-number-field-stepper-icon);
7509
+
7510
+ cursor: pointer;
7511
+ -webkit-appearance: none;
7512
+ appearance: none;
7513
+ -webkit-tap-highlight-color: transparent;
7514
+ }
7515
+
7516
+ /* `display` above would beat the attribute - without JS the buttons don't show */
7517
+ .al-number-field__step[hidden] {
7518
+ display: none;
7519
+ }
7520
+
7521
+ .al-number-field__step > svg {
7522
+ display: block;
7523
+ flex: none;
7524
+ width: var(--al-number-field-icon-size);
7525
+ height: var(--al-number-field-icon-size);
7526
+ }
7527
+
7528
+ /* at the limit, and in a disabled field - rule 10 */
7529
+ .al-number-field__step:disabled {
7530
+ color: var(--al-number-field-stepper-icon-disabled);
7531
+ cursor: not-allowed;
7532
+ }
7533
+
7534
+ /* ------------------------------------------------------------ help text */
7535
+ .al-number-field__help {
7536
+ color: var(--al-number-field-help);
7537
+ font-family: var(--al-font-sans);
7538
+ font-size: var(--al-number-field-help-font-size);
7539
+ line-height: var(--al-number-field-help-line-height);
7540
+ font-weight: var(--al-number-field-help-font-weight);
7541
+ letter-spacing: var(--al-number-field-help-tracking);
7542
+ margin: 0;
7543
+ }
7544
+
7545
+ /* -------------------------------------------------------------- states */
7546
+ .al-number-field__control:hover:not(:has(.al-number-field__field:disabled, .al-number-field__field[readonly])) {
7547
+ --_border-state: var(--al-number-field-border-hover);
7548
+ }
7549
+
7550
+ .al-number-field__control:has(.al-number-field__field:focus-visible) {
7551
+ --_border-state: var(--al-number-field-border-focus);
7552
+ box-shadow: var(--_ring-force, var(--al-number-field-ring));
7553
+ }
7554
+
7555
+ /* error - beats hover and focus by writing to another property */
7556
+ .al-number-field__control:has(.al-number-field__field[aria-invalid="true"]) {
7557
+ --_border-force: var(--al-number-field-border-error);
7558
+ --_ring-force: var(--al-number-field-ring-error);
7559
+ }
7560
+
7561
+ .al-number-field:has(.al-number-field__field[aria-invalid="true"]) .al-number-field__label {
7562
+ color: var(--al-number-field-label-error);
7563
+ }
7564
+
7565
+ .al-number-field:has(.al-number-field__field[aria-invalid="true"]) .al-number-field__help {
7566
+ color: var(--al-number-field-help-error);
7567
+ }
7568
+
7569
+ /* read-only - `[readonly]`, never `:read-only`. No steppers - rule 17. */
7570
+ .al-number-field__control:has(.al-number-field__field[readonly]) {
7571
+ --_border-force: var(--al-number-field-border-readonly);
7572
+ --_bg-force: var(--al-number-field-bg-readonly);
7573
+ cursor: default;
7574
+ }
7575
+
7576
+ .al-number-field__control:has(.al-number-field__field[readonly]) .al-number-field__step {
7577
+ display: none;
7578
+ }
7579
+
7580
+ /* disabled - last, because it wins over all */
7581
+ .al-number-field__control:has(.al-number-field__field:disabled) {
7582
+ --_border-force: var(--al-number-field-border-disabled);
7583
+ --_bg-force: var(--al-number-field-bg-disabled);
7584
+ cursor: not-allowed;
7585
+ }
7586
+
7587
+ .al-number-field__field:disabled {
7588
+ --_text-force: var(--al-number-field-text-disabled);
7589
+ cursor: not-allowed;
7590
+ }
7591
+
7592
+ .al-number-field:has(.al-number-field__field:disabled) .al-number-field__label,
7593
+ .al-number-field:has(.al-number-field__field:disabled) .al-number-field__optional {
7594
+ color: var(--al-number-field-label-disabled);
7595
+ }
7596
+
7597
+ /* ENTER - CSS uses the curve of the state the transition goes to. */
7598
+ .al-number-field__control:hover,
7599
+ .al-number-field__control:has(.al-number-field__field:focus-visible),
7600
+ .al-number-field__control:has(.al-number-field__field[aria-invalid="true"]) {
7601
+ transition-timing-function: var(--al-motion-easing-enter);
7602
+ }
7603
+
7604
+ /* --------------------------------------------------------- environment */
7605
+ @media (prefers-reduced-motion: reduce) {
7606
+ .al-number-field__control {
7607
+ transition: none;
7608
+ }
7609
+ }
@@ -0,0 +1,363 @@
1
+ /* AL Design System - Number field tokens
2
+ * GENERATED by src/components/number-field/tokens.py. Do not edit by hand.
3
+ *
4
+ * There is no theme block here: each token points to a semantic, and the
5
+ * theme switches on :root - the same element where these aliases are declared.
6
+ *
7
+ * Field height: 48px, derived, no token.
8
+ * Stepper target: 24px = icon-size, no token of its own.
9
+ */
10
+
11
+ :root {
12
+
13
+ /* background - only disabled and readonly change it */
14
+ --al-number-field-bg: var(--al-bg-surface-raised);
15
+ --al-number-field-bg-disabled: var(--al-bg-disabled);
16
+ --al-number-field-bg-readonly: var(--al-bg-subtle);
17
+
18
+ /* border - the axis that carries the state */
19
+ --al-number-field-border: var(--al-border-default);
20
+ --al-number-field-border-hover: var(--al-border-strong);
21
+ --al-number-field-border-focus: var(--al-border-brand);
22
+ --al-number-field-border-error: var(--al-border-danger);
23
+ --al-number-field-border-disabled: var(--al-border-default);
24
+ --al-number-field-border-readonly: var(--al-border-subtle);
25
+
26
+ /* value - no placeholder, the field always has a value */
27
+ --al-number-field-text-value: var(--al-text-primary);
28
+ --al-number-field-text-disabled: var(--al-text-disabled);
29
+
30
+ /* steppers - out of the Tab order, no hover or ring of their own */
31
+ --al-number-field-stepper-icon: var(--al-text-primary);
32
+ --al-number-field-stepper-icon-disabled: var(--al-text-disabled);
33
+
34
+ /* label, mark and help - they live outside the field, on the canvas */
35
+ --al-number-field-label: var(--al-text-primary);
36
+ --al-number-field-label-error: var(--al-text-danger);
37
+ --al-number-field-label-disabled: var(--al-text-disabled);
38
+ --al-number-field-optional: var(--al-text-secondary);
39
+ --al-number-field-help: var(--al-text-secondary);
40
+ --al-number-field-help-error: var(--al-text-danger);
41
+
42
+ /* focus ring - points to the composite shadow, not the raw color */
43
+ --al-number-field-ring: var(--al-focus-ring-default);
44
+ --al-number-field-ring-error: var(--al-focus-ring-error);
45
+
46
+ /* geometry */
47
+ --al-number-field-padding-x: var(--al-space-12);
48
+ --al-number-field-padding-y: var(--al-space-12);
49
+ --al-number-field-gap: var(--al-space-8);
50
+ --al-number-field-stepper-gap: var(--al-space-24);
51
+ --al-number-field-radius: var(--al-radius-lg);
52
+ --al-number-field-border-width: var(--al-border-width-1);
53
+ --al-number-field-icon-size: var(--al-icon-size-24);
54
+
55
+ /* width of the box - declared literal, the Foundation has no width scale */
56
+ --al-number-field-width: 140px;
57
+
58
+ /* typography - one style becomes four vars */
59
+ --al-number-field-font-size: var(--al-font-size-md);
60
+ --al-number-field-line-height: var(--al-line-height-md);
61
+ --al-number-field-font-weight: 400;
62
+ --al-number-field-tracking: 0em;
63
+ --al-number-field-label-font-size: var(--al-font-size-md);
64
+ --al-number-field-label-line-height: var(--al-line-height-md);
65
+ --al-number-field-label-font-weight: 400;
66
+ --al-number-field-label-tracking: 0em;
67
+ --al-number-field-optional-font-size: var(--al-font-size-sm);
68
+ --al-number-field-optional-line-height: var(--al-line-height-sm);
69
+ --al-number-field-optional-font-weight: 400;
70
+ --al-number-field-optional-tracking: 0em;
71
+ --al-number-field-help-font-size: var(--al-font-size-xs);
72
+ --al-number-field-help-line-height: var(--al-line-height-xs);
73
+ --al-number-field-help-font-weight: 400;
74
+ --al-number-field-help-tracking: 0.01em;
75
+ }
76
+
77
+ /* AL Design System - Number field
78
+ *
79
+ * Depends on: foundation/al-foundation.css
80
+ * + components/number-field/al-number-field-tokens.css
81
+ * + components/icon/icon.css
82
+ * + components/number-field/number-field.js (the steppers)
83
+ *
84
+ * A QUANTITY THE PERSON ADJUSTS A LITTLE AT A TIME
85
+ * A native <input type="number"> between a - and a + button. Numbers that
86
+ * are not quantities (zip code, phone, card) are the Input with
87
+ * inputmode="numeric" - rule 2.
88
+ *
89
+ * ANATOMY
90
+ * <div class="al-number-field">
91
+ * <div class="al-number-field__labelrow">
92
+ * <label class="al-number-field__label" for="qty">Quantity</label>
93
+ * <span class="al-number-field__optional">(Optional)</span>
94
+ * </div>
95
+ * <div class="al-number-field__control">
96
+ * <button class="al-number-field__step" type="button" tabindex="-1"
97
+ * data-step="down" aria-controls="qty"
98
+ * aria-label="Decrease quantity" hidden>
99
+ * <svg class="al-icon" aria-hidden="true" focusable="false">minus</svg>
100
+ * </button>
101
+ * <input class="al-number-field__field" id="qty" type="number"
102
+ * min="1" max="10" step="1" value="1"
103
+ * aria-describedby="qty-help">
104
+ * <button class="al-number-field__step" type="button" tabindex="-1"
105
+ * data-step="up" aria-controls="qty"
106
+ * aria-label="Increase quantity" hidden>
107
+ * <svg class="al-icon" aria-hidden="true" focusable="false">plus</svg>
108
+ * </button>
109
+ * </div>
110
+ * <p class="al-number-field__help" id="qty-help">From 1 to 10</p>
111
+ * </div>
112
+ *
113
+ * THE STEPPERS ARE OUT OF THE TAB ORDER
114
+ * One tab stop: the arrow keys and typing change the value (Carbon, rule
115
+ * 13). So the buttons carry tabindex="-1", have no ring and no hover of
116
+ * their own (rule 14) - the field's border and ring say where pointer and
117
+ * focus are. They are born `hidden`: without JavaScript the arrow keys
118
+ * still work and a button that does nothing never shows (the same as the
119
+ * Password's eye).
120
+ *
121
+ * THE STEPPER AT THE LIMIT IS A DISABLED <button>
122
+ * At the minimum the - is `disabled`, at the maximum the + (rule 10). The
123
+ * JS sets the attribute; the CSS only reads `:disabled`. There is no Figma
124
+ * variant for it.
125
+ *
126
+ * THE BOX IS `__control`, NOT THE <input>
127
+ * The same as the Input: border, background, radius and ring live on
128
+ * `__control` and the state is read through `:has()`. Without `:has()` the
129
+ * native outline stays (it is only removed inside @supports).
130
+ *
131
+ * THE STATE IS DRIVEN BY THE MARKUP
132
+ * Error reads `aria-invalid="true"`; disabled `:disabled`; read-only
133
+ * `[readonly]` - never `:read-only`, which also matches disabled. The same
134
+ * precedence by variable as the Input: error, read-only, disabled in file
135
+ * order, disabled wins over all.
136
+ *
137
+ * READ-ONLY HAS NO STEPPERS (rule 17)
138
+ * Nothing can change, so they are hidden and the value stays centered.
139
+ *
140
+ * THE BROWSER'S OWN SPINNERS ARE HIDDEN
141
+ * WebKit draws arrows inside type=number and Firefox draws its own. With
142
+ * AL's buttons there would be two pairs.
143
+ *
144
+ * HEIGHT AND WIDTH
145
+ * 48 = padding-y x2 + line height, border subtracted from the padding.
146
+ * The box is fixed at `--al-number-field-width` (140), decided on
147
+ * 2026-10-21. Label and help text are not limited by it - they may be
148
+ * wider than the box.
149
+ *
150
+ * HANDWRITTEN. The tokens it consumes are generated by tokens.py.
151
+ */
152
+
153
+ /* ---------------------------------------------------------------- root */
154
+ .al-number-field {
155
+ display: flex;
156
+ flex-direction: column;
157
+ gap: var(--al-number-field-gap);
158
+ }
159
+
160
+ /* ------------------------------------------------------------ label */
161
+ .al-number-field__labelrow {
162
+ display: flex;
163
+ align-items: baseline;
164
+ gap: var(--al-number-field-gap);
165
+ }
166
+
167
+ .al-number-field__label {
168
+ color: var(--al-number-field-label);
169
+ font-family: var(--al-font-sans);
170
+ font-size: var(--al-number-field-label-font-size);
171
+ line-height: var(--al-number-field-label-line-height);
172
+ font-weight: var(--al-number-field-label-font-weight);
173
+ letter-spacing: var(--al-number-field-label-tracking);
174
+ }
175
+
176
+ .al-number-field__optional {
177
+ color: var(--al-number-field-optional);
178
+ font-family: var(--al-font-sans);
179
+ font-size: var(--al-number-field-optional-font-size);
180
+ line-height: var(--al-number-field-optional-line-height);
181
+ font-weight: var(--al-number-field-optional-font-weight);
182
+ letter-spacing: var(--al-number-field-optional-tracking);
183
+ }
184
+
185
+ /* --------------------------------------------------------------- box */
186
+ .al-number-field__control {
187
+ --_border-state: var(--al-number-field-border);
188
+
189
+ box-sizing: border-box;
190
+ display: flex;
191
+ align-items: center;
192
+ gap: var(--al-number-field-stepper-gap);
193
+ width: var(--al-number-field-width);
194
+
195
+ padding: calc(var(--al-number-field-padding-y) - var(--al-number-field-border-width))
196
+ calc(var(--al-number-field-padding-x) - var(--al-number-field-border-width));
197
+
198
+ border: var(--al-number-field-border-width) solid
199
+ var(--_border-force, var(--_border-state));
200
+ border-radius: var(--al-number-field-radius);
201
+ background-color: var(--_bg-force, var(--al-number-field-bg));
202
+ cursor: text;
203
+
204
+ transition:
205
+ border-color var(--al-motion-duration-feedback) var(--al-motion-easing-exit),
206
+ box-shadow var(--al-motion-duration-feedback) var(--al-motion-easing-exit);
207
+ }
208
+
209
+ /* ------------------------------------------------------------- field */
210
+ .al-number-field__field {
211
+ --_text-state: var(--al-number-field-text-value);
212
+
213
+ flex: 1 1 0;
214
+ min-width: 0;
215
+ width: 100%;
216
+ margin: 0;
217
+ padding: 0;
218
+ border: 0;
219
+ background: none;
220
+ color: var(--_text-force, var(--_text-state));
221
+ text-align: center;
222
+
223
+ font-family: var(--al-font-sans);
224
+ font-size: var(--al-number-field-font-size);
225
+ line-height: var(--al-number-field-line-height);
226
+ font-weight: var(--al-number-field-font-weight);
227
+ letter-spacing: var(--al-number-field-tracking);
228
+ font-variant-numeric: tabular-nums;
229
+
230
+ -moz-appearance: textfield;
231
+ appearance: textfield;
232
+ }
233
+
234
+ /* the browser's own spinners - see the header */
235
+ .al-number-field__field::-webkit-inner-spin-button,
236
+ .al-number-field__field::-webkit-outer-spin-button {
237
+ -webkit-appearance: none;
238
+ margin: 0;
239
+ }
240
+
241
+ @supports selector(:has(*)) {
242
+ .al-number-field__field:focus {
243
+ outline: none;
244
+ }
245
+ }
246
+
247
+ /* --------------------------------------------------------- the steppers */
248
+ .al-number-field__step {
249
+ display: inline-flex;
250
+ align-items: center;
251
+ justify-content: center;
252
+ flex: none;
253
+
254
+ box-sizing: border-box;
255
+ width: var(--al-number-field-icon-size);
256
+ height: var(--al-number-field-icon-size);
257
+ margin: 0;
258
+ padding: 0;
259
+ border: 0;
260
+ background: none;
261
+ /* the svg inherits from here through currentColor - the Icon contract */
262
+ color: var(--al-number-field-stepper-icon);
263
+
264
+ cursor: pointer;
265
+ -webkit-appearance: none;
266
+ appearance: none;
267
+ -webkit-tap-highlight-color: transparent;
268
+ }
269
+
270
+ /* `display` above would beat the attribute - without JS the buttons don't show */
271
+ .al-number-field__step[hidden] {
272
+ display: none;
273
+ }
274
+
275
+ .al-number-field__step > svg {
276
+ display: block;
277
+ flex: none;
278
+ width: var(--al-number-field-icon-size);
279
+ height: var(--al-number-field-icon-size);
280
+ }
281
+
282
+ /* at the limit, and in a disabled field - rule 10 */
283
+ .al-number-field__step:disabled {
284
+ color: var(--al-number-field-stepper-icon-disabled);
285
+ cursor: not-allowed;
286
+ }
287
+
288
+ /* ------------------------------------------------------------ help text */
289
+ .al-number-field__help {
290
+ color: var(--al-number-field-help);
291
+ font-family: var(--al-font-sans);
292
+ font-size: var(--al-number-field-help-font-size);
293
+ line-height: var(--al-number-field-help-line-height);
294
+ font-weight: var(--al-number-field-help-font-weight);
295
+ letter-spacing: var(--al-number-field-help-tracking);
296
+ margin: 0;
297
+ }
298
+
299
+ /* -------------------------------------------------------------- states */
300
+ .al-number-field__control:hover:not(:has(.al-number-field__field:disabled, .al-number-field__field[readonly])) {
301
+ --_border-state: var(--al-number-field-border-hover);
302
+ }
303
+
304
+ .al-number-field__control:has(.al-number-field__field:focus-visible) {
305
+ --_border-state: var(--al-number-field-border-focus);
306
+ box-shadow: var(--_ring-force, var(--al-number-field-ring));
307
+ }
308
+
309
+ /* error - beats hover and focus by writing to another property */
310
+ .al-number-field__control:has(.al-number-field__field[aria-invalid="true"]) {
311
+ --_border-force: var(--al-number-field-border-error);
312
+ --_ring-force: var(--al-number-field-ring-error);
313
+ }
314
+
315
+ .al-number-field:has(.al-number-field__field[aria-invalid="true"]) .al-number-field__label {
316
+ color: var(--al-number-field-label-error);
317
+ }
318
+
319
+ .al-number-field:has(.al-number-field__field[aria-invalid="true"]) .al-number-field__help {
320
+ color: var(--al-number-field-help-error);
321
+ }
322
+
323
+ /* read-only - `[readonly]`, never `:read-only`. No steppers - rule 17. */
324
+ .al-number-field__control:has(.al-number-field__field[readonly]) {
325
+ --_border-force: var(--al-number-field-border-readonly);
326
+ --_bg-force: var(--al-number-field-bg-readonly);
327
+ cursor: default;
328
+ }
329
+
330
+ .al-number-field__control:has(.al-number-field__field[readonly]) .al-number-field__step {
331
+ display: none;
332
+ }
333
+
334
+ /* disabled - last, because it wins over all */
335
+ .al-number-field__control:has(.al-number-field__field:disabled) {
336
+ --_border-force: var(--al-number-field-border-disabled);
337
+ --_bg-force: var(--al-number-field-bg-disabled);
338
+ cursor: not-allowed;
339
+ }
340
+
341
+ .al-number-field__field:disabled {
342
+ --_text-force: var(--al-number-field-text-disabled);
343
+ cursor: not-allowed;
344
+ }
345
+
346
+ .al-number-field:has(.al-number-field__field:disabled) .al-number-field__label,
347
+ .al-number-field:has(.al-number-field__field:disabled) .al-number-field__optional {
348
+ color: var(--al-number-field-label-disabled);
349
+ }
350
+
351
+ /* ENTER - CSS uses the curve of the state the transition goes to. */
352
+ .al-number-field__control:hover,
353
+ .al-number-field__control:has(.al-number-field__field:focus-visible),
354
+ .al-number-field__control:has(.al-number-field__field[aria-invalid="true"]) {
355
+ transition-timing-function: var(--al-motion-easing-enter);
356
+ }
357
+
358
+ /* --------------------------------------------------------- environment */
359
+ @media (prefers-reduced-motion: reduce) {
360
+ .al-number-field__control {
361
+ transition: none;
362
+ }
363
+ }
@@ -0,0 +1,83 @@
1
+ /* AL Design System - Number field: the steppers' behavior
2
+ *
3
+ * The arrow keys and typing are native to <input type="number">. This file
4
+ * only does what the browser doesn't:
5
+ *
6
+ * 1. removes the buttons' `hidden` (without JS they don't exist);
7
+ * 2. - and + call stepDown()/stepUp() and fire `input` and `change`, so the
8
+ * form hears the change like it was typed. One click is one step:
9
+ * holding the button does NOT repeat (decision E=a);
10
+ * 3. keeps focus where it was - clicking a stepper doesn't move focus to
11
+ * the field or to the button (rule 15);
12
+ * 4. disables the stepper at the limit: - at the minimum, + at the maximum
13
+ * (rule 10). A disabled field disables both.
14
+ *
15
+ * It never clamps what was typed (decision F=a): a value out of range stays
16
+ * as typed, and the form shows the error by reading `field.validity`
17
+ * (rangeUnderflow / rangeOverflow) and setting aria-invalid + the message.
18
+ *
19
+ * It starts by itself on load. Content inserted later:
20
+ * window.alNumberField.init(elementContainingTheFields)
21
+ */
22
+ (function () {
23
+ 'use strict';
24
+
25
+ function setup(root) {
26
+ var field = root.querySelector('.al-number-field__field');
27
+ var down = root.querySelector('.al-number-field__step[data-step="down"]');
28
+ var up = root.querySelector('.al-number-field__step[data-step="up"]');
29
+ if (!field || !down || !up || root.hasAttribute('data-al-ready')) return;
30
+ root.setAttribute('data-al-ready', '');
31
+
32
+ function bound(attr) {
33
+ var v = field.getAttribute(attr);
34
+ return v === null || v === '' ? null : Number(v);
35
+ }
36
+
37
+ function sync() {
38
+ var value = field.valueAsNumber;
39
+ var min = bound('min');
40
+ var max = bound('max');
41
+ var off = field.disabled || field.readOnly;
42
+ down.disabled = off || (min !== null && !isNaN(value) && value <= min);
43
+ up.disabled = off || (max !== null && !isNaN(value) && value >= max);
44
+ }
45
+
46
+ function step(dir) {
47
+ if (field.disabled || field.readOnly) return;
48
+ try {
49
+ if (dir === 'up') field.stepUp(); else field.stepDown();
50
+ } catch (e) {
51
+ return; // an empty or invalid value - stepping has nothing to start from
52
+ }
53
+ field.dispatchEvent(new Event('input', { bubbles: true }));
54
+ field.dispatchEvent(new Event('change', { bubbles: true }));
55
+ sync();
56
+ }
57
+
58
+ [down, up].forEach(function (button) {
59
+ // rule 15: the click must not take focus from where it was
60
+ button.addEventListener('mousedown', function (e) { e.preventDefault(); });
61
+ button.addEventListener('click', function () {
62
+ step(button.getAttribute('data-step'));
63
+ });
64
+ button.hidden = false;
65
+ });
66
+
67
+ field.addEventListener('input', sync);
68
+ sync();
69
+ }
70
+
71
+ function init(scope) {
72
+ var roots = (scope || document).querySelectorAll('.al-number-field');
73
+ for (var i = 0; i < roots.length; i++) setup(roots[i]);
74
+ }
75
+
76
+ window.alNumberField = { init: init };
77
+
78
+ if (document.readyState === 'loading') {
79
+ document.addEventListener('DOMContentLoaded', function () { init(); });
80
+ } else {
81
+ init();
82
+ }
83
+ })();
@@ -0,0 +1,196 @@
1
+ /* AL Design System - Progress tokens
2
+ * GENERATED by src/components/pagination/tokens.py. Do not edit by hand.
3
+ *
4
+ * Every color is a semantic the theme switches on :root.
5
+ */
6
+
7
+ :root {
8
+ /* color */
9
+ --al-pagination-item-label: var(--al-text-primary);
10
+ --al-pagination-item-bg-hover: var(--al-bg-hover);
11
+ --al-pagination-item-bg-active: var(--al-bg-active);
12
+ --al-pagination-ellipsis: var(--al-text-secondary);
13
+ --al-pagination-current-bg: var(--al-bg-brand-subtle);
14
+ --al-pagination-current-label: var(--al-text-brand);
15
+ --al-pagination-current-bg-hover: var(--al-bg-brand-hover);
16
+ --al-pagination-current-bg-active: var(--al-bg-brand-active);
17
+ --al-pagination-current-label-hover: var(--al-text-on-brand);
18
+ --al-pagination-range-text: var(--al-text-secondary);
19
+
20
+ /* geometry */
21
+ --al-pagination-item-padding: var(--al-space-8);
22
+ --al-pagination-item-radius: var(--al-radius-full);
23
+ --al-pagination-gap: var(--al-space-4);
24
+ --al-pagination-range-gap: var(--al-space-16);
25
+ --al-pagination-rows-gap: var(--al-space-8);
26
+
27
+ /* type - page numbers like the Button sm, range like body-sm */
28
+ --al-pagination-font-size: var(--al-font-size-sm);
29
+ --al-pagination-line-height: var(--al-line-height-sm);
30
+ --al-pagination-font-weight: var(--al-font-weight-medium);
31
+ --al-pagination-range-font-weight: var(--al-font-weight-regular);
32
+ }
33
+
34
+ /* AL Design System - Pagination
35
+ *
36
+ * Four types, one block. Previous, Next and Load more are Buttons (Ghost sm,
37
+ * Secondary sm) and rows per page is the Select: this file only lays them out
38
+ * and draws what is the Pagination's own - the page items and the range text.
39
+ *
40
+ * .al-pagination Numbered - previous, pages, next
41
+ * .al-pagination--simple Simple - previous and next only
42
+ * .al-pagination--load-more Load more - one Secondary Button
43
+ * .al-pagination--range Range - "Showing 41-60 of 312" + rows
44
+ *
45
+ * Markup (usage rules 20-22): a <nav aria-label="Pagination"> with the pages
46
+ * in a <ul>. A page is an <a> when it changes the URL and a <button> when it
47
+ * swaps content in place. The current page carries aria-current="page" - that
48
+ * is what styles it, so the look can never disagree with what is announced.
49
+ * The ellipsis is aria-hidden: it is not a control (rule 10).
50
+ *
51
+ * Disabled previous/next is the Button's own disabled state (rule 16), not a
52
+ * Pagination style.
53
+ *
54
+ * NEVER ON SURFACE-RAISED (rule 8): the hovers use bg-hover/bg-active, which
55
+ * vanish on the raised surface.
56
+ *
57
+ * NO HEIGHT: 36 = padding 8 + line 20 + padding 8. min-inline-size uses the
58
+ * same sum so a one-digit page stays a circle and "16" grows into a pill.
59
+ */
60
+
61
+ /* ONE LINE (decided with Gui on 2026-10-21): wrapping split Previous, the
62
+ pages and Next into three rows. The row never wraps; when it does not fit,
63
+ the CONTAINER scrolls sideways (overflow-x: auto, with room for the focus
64
+ ring). Range is the exception: text and rows may wrap. */
65
+ .al-pagination {
66
+ display: flex;
67
+ flex-wrap: nowrap;
68
+ align-items: center;
69
+ gap: var(--al-pagination-gap);
70
+ inline-size: max-content;
71
+ }
72
+
73
+ .al-pagination--range {
74
+ flex-wrap: wrap;
75
+ inline-size: auto;
76
+ }
77
+
78
+ .al-pagination__pages {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: var(--al-pagination-gap);
82
+ margin: 0;
83
+ padding: 0;
84
+ list-style: none;
85
+ }
86
+
87
+ /* ------------------------------------------------------------- page item */
88
+ .al-pagination__item {
89
+ display: inline-flex;
90
+ align-items: center;
91
+ justify-content: center;
92
+ box-sizing: border-box;
93
+ min-inline-size: calc(var(--al-pagination-item-padding) * 2 + var(--al-pagination-line-height));
94
+ margin: 0;
95
+ padding: var(--al-pagination-item-padding);
96
+ border: 0;
97
+ border-radius: var(--al-pagination-item-radius);
98
+ background-color: transparent;
99
+ font-family: var(--al-font-sans);
100
+ font-size: var(--al-pagination-font-size);
101
+ line-height: var(--al-pagination-line-height);
102
+ font-weight: var(--al-pagination-font-weight);
103
+ font-variant-numeric: tabular-nums;
104
+ color: var(--al-pagination-item-label);
105
+ text-decoration: none;
106
+ cursor: pointer;
107
+ -webkit-tap-highlight-color: transparent;
108
+ transition: background-color var(--al-motion-duration-feedback) var(--al-motion-easing-exit), color var(--al-motion-duration-feedback) var(--al-motion-easing-exit), box-shadow var(--al-motion-duration-feedback) var(--al-motion-easing-exit);
109
+ }
110
+
111
+ .al-pagination__item:hover {
112
+ background-color: var(--al-pagination-item-bg-hover);
113
+ }
114
+
115
+ .al-pagination__item:active {
116
+ background-color: var(--al-pagination-item-bg-active);
117
+ }
118
+
119
+ /* focus is rest plus the ring, keyboard only */
120
+ .al-pagination__item:focus-visible {
121
+ outline: none;
122
+ box-shadow: var(--al-focus-ring-default);
123
+ }
124
+
125
+ /* current: tonal at rest and focus, brand on hover and pressed (rule 19) */
126
+ .al-pagination__item[aria-current="page"] {
127
+ background-color: var(--al-pagination-current-bg);
128
+ color: var(--al-pagination-current-label);
129
+ }
130
+
131
+ .al-pagination__item[aria-current="page"]:hover {
132
+ background-color: var(--al-pagination-current-bg-hover);
133
+ color: var(--al-pagination-current-label-hover);
134
+ }
135
+
136
+ .al-pagination__item[aria-current="page"]:active {
137
+ background-color: var(--al-pagination-current-bg-active);
138
+ color: var(--al-pagination-current-label-hover);
139
+ }
140
+
141
+ /* ENTER - destination states take the enter curve, as in the Tab */
142
+ .al-pagination__item:hover,
143
+ .al-pagination__item:active,
144
+ .al-pagination__item:focus-visible {
145
+ transition-timing-function: var(--al-motion-easing-enter);
146
+ }
147
+
148
+ /* ------------------------------------------------------------- ellipsis */
149
+ .al-pagination__ellipsis {
150
+ display: inline-flex;
151
+ align-items: center;
152
+ justify-content: center;
153
+ min-inline-size: calc(var(--al-pagination-item-padding) * 2 + var(--al-pagination-line-height));
154
+ padding: var(--al-pagination-item-padding);
155
+ box-sizing: border-box;
156
+ color: var(--al-pagination-ellipsis);
157
+ }
158
+
159
+ /* ---------------------------------------------------------------- range */
160
+ .al-pagination--range {
161
+ gap: var(--al-pagination-range-gap);
162
+ }
163
+
164
+ .al-pagination__range,
165
+ .al-pagination__rows-label {
166
+ margin: 0;
167
+ font-family: var(--al-font-sans);
168
+ font-size: var(--al-pagination-font-size);
169
+ line-height: var(--al-pagination-line-height);
170
+ font-weight: var(--al-pagination-range-font-weight);
171
+ font-variant-numeric: tabular-nums;
172
+ color: var(--al-pagination-range-text);
173
+ }
174
+
175
+ .al-pagination__rows {
176
+ display: flex;
177
+ align-items: center;
178
+ gap: var(--al-pagination-rows-gap);
179
+ }
180
+
181
+ @media (prefers-reduced-motion: reduce) {
182
+ .al-pagination__item {
183
+ transition: none;
184
+ }
185
+ }
186
+
187
+ @media (forced-colors: active) {
188
+ .al-pagination__item[aria-current="page"] {
189
+ outline: var(--al-border-width-1) solid CanvasText;
190
+ }
191
+
192
+ .al-pagination__item:focus-visible {
193
+ outline: var(--al-border-width-focus) solid Highlight;
194
+ outline-offset: var(--al-border-focus-offset);
195
+ }
196
+ }
@@ -1,5 +1,5 @@
1
1
  /* AL Design System - Foundation
2
- * v1.2.0 - MIT
2
+ * v1.4.0 - MIT
3
3
  * GENERATED by src/foundation/css.py. Do not edit by hand: run the script.
4
4
  */
5
5
 
package/dist/tokens.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "meta": {
3
3
  "name": "AL Design System",
4
- "version": "1.2.0",
4
+ "version": "1.4.0",
5
5
  "license": "MIT",
6
6
  "brandAnchor": "#FC5000",
7
7
  "colorSpace": "OKLCH",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "al-design-system",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "AL Design System: Foundation and components in plain CSS, from Figma to code.",
5
5
  "license": "MIT",
6
6
  "author": "Guilherme Domingues",