@shift-css/core 0.4.0 → 0.5.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 ADDED
@@ -0,0 +1,110 @@
1
+ # @shift-css/core
2
+
3
+ A zero-runtime, OKLCH-native CSS framework with automatic theming.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@shift-css/core)](https://www.npmjs.com/package/@shift-css/core)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install @shift-css/core
12
+ # or
13
+ bun add @shift-css/core
14
+ # or
15
+ pnpm add @shift-css/core
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```css
21
+ /* Full framework */
22
+ @import "@shift-css/core";
23
+
24
+ /* Or minified for production */
25
+ @import "@shift-css/core/min";
26
+ ```
27
+
28
+ ```html
29
+ <div s-surface="raised">
30
+ <h2>Hello Shift</h2>
31
+ <button s-btn="primary">Get Started</button>
32
+ </div>
33
+ ```
34
+
35
+ No configuration, no JavaScript, no build step for customization.
36
+
37
+ ## Features
38
+
39
+ - **Zero JavaScript** - Theming works without any runtime code
40
+ - **Perceptually uniform colors** - OKLCH ensures consistent brightness across your palette
41
+ - **Automatic contrast** - Text colors adapt to any background automatically
42
+ - **Clean specificity** - Cascade layers mean no more `!important` battles
43
+ - **One-line customization** - Change a single hue variable to transform your entire palette
44
+
45
+ ```css
46
+ /* Change your entire brand palette with one line */
47
+ :root {
48
+ --shift-hue-primary: 280; /* Purple instead of blue */
49
+ }
50
+ ```
51
+
52
+ ## Customization
53
+
54
+ Override seed hues to transform your entire palette:
55
+
56
+ ```css
57
+ :root {
58
+ --shift-hue-primary: 280; /* Purple */
59
+ --shift-hue-secondary: 200; /* Teal */
60
+ --shift-hue-accent: 45; /* Gold */
61
+ --shift-hue-neutral: 280; /* Purple-tinted grays */
62
+ }
63
+ ```
64
+
65
+ ## Modular Imports
66
+
67
+ Import only what you need:
68
+
69
+ ```css
70
+ /* Reset only */
71
+ @import "@shift-css/core/reset";
72
+
73
+ /* Tokens only */
74
+ @import "@shift-css/core/tokens";
75
+ ```
76
+
77
+ ## Bundle Size
78
+
79
+ | File | Size |
80
+ | --------------- | ------ |
81
+ | `shift.css` | ~55 KB |
82
+ | `shift.min.css` | ~41 KB |
83
+ | Gzipped | ~10 KB |
84
+
85
+ ## Browser Support
86
+
87
+ | Browser | Minimum Version |
88
+ | ------- | --------------- |
89
+ | Chrome | 131+ |
90
+ | Firefox | 133+ |
91
+ | Safari | 18+ |
92
+ | Edge | 131+ |
93
+
94
+ ## Documentation
95
+
96
+ Full documentation at [getshiftcss.com](https://getshiftcss.com)
97
+
98
+ ## CLI
99
+
100
+ Use the CLI to initialize Shift CSS with proper cascade layers:
101
+
102
+ ```bash
103
+ npx shift-css init
104
+ ```
105
+
106
+ See [@shift-css/cli](https://www.npmjs.com/package/@shift-css/cli) for details.
107
+
108
+ ## License
109
+
110
+ MIT
@@ -0,0 +1,216 @@
1
+ :where([s-btn]) {
2
+ --_btn-bg: var(--s-interactive-primary);
3
+ --_btn-text: var(--s-text-inverse);
4
+ --_btn-border: transparent;
5
+ justify-content: center;
6
+ align-items: center;
7
+ gap: var(--s-space-2);
8
+ padding: var(--s-space-2) var(--s-space-4);
9
+ border-radius: var(--s-radius-md);
10
+ font-weight: var(--s-font-semibold);
11
+ font-size: var(--s-text-sm);
12
+ white-space: nowrap;
13
+ background-color: var(--_btn-bg);
14
+ color: var(--_btn-text);
15
+ border: 1px solid var(--_btn-border);
16
+ line-height: 1;
17
+ display: inline-flex;
18
+
19
+ @supports (color: oklch(from red l c h)) {
20
+ color: oklch(from var(--_btn-bg) clamp(.15, calc((.55 - l) * 1000 + .15), .95) 0 0);
21
+ }
22
+
23
+ @supports (color: contrast-color(red)) {
24
+ color: contrast-color(var(--_btn-bg));
25
+ }
26
+
27
+ transition: background-color var(--s-duration-150) var(--s-ease-out), border-color var(--s-duration-150) var(--s-ease-out), transform var(--s-duration-100) var(--s-ease-out);
28
+ cursor: pointer;
29
+ user-select: none;
30
+
31
+ &:hover:not(:disabled) {
32
+ --_btn-bg: var(--s-interactive-primary-hover);
33
+ }
34
+
35
+ &:active:not(:disabled) {
36
+ --_btn-bg: var(--s-interactive-primary-active);
37
+ transform: scale(.98);
38
+ }
39
+
40
+ &:focus-visible {
41
+ outline: 2px solid var(--s-focus-ring);
42
+ outline-offset: 2px;
43
+ }
44
+
45
+ &:disabled {
46
+ opacity: .5;
47
+ cursor: not-allowed;
48
+ pointer-events: none;
49
+ }
50
+ }
51
+
52
+ [s-btn="primary"] {
53
+ --_btn-bg: var(--s-interactive-primary);
54
+ --_btn-text: var(--s-text-inverse);
55
+
56
+ &:hover:not(:disabled) {
57
+ --_btn-bg: var(--s-interactive-primary-hover);
58
+ }
59
+
60
+ &:active:not(:disabled) {
61
+ --_btn-bg: var(--s-interactive-primary-active);
62
+ }
63
+ }
64
+
65
+ [s-btn="secondary"] {
66
+ --_btn-bg: var(--s-surface-raised);
67
+ --_btn-text: var(--s-text-primary);
68
+ --_btn-border: var(--s-border-default);
69
+ color: var(--_btn-text);
70
+
71
+ &:hover:not(:disabled) {
72
+ --_btn-bg: var(--s-surface-sunken);
73
+ }
74
+ }
75
+
76
+ [s-btn="ghost"] {
77
+ --_btn-bg: transparent;
78
+ --_btn-text: var(--s-interactive-primary);
79
+ color: var(--_btn-text);
80
+
81
+ &:hover:not(:disabled) {
82
+ --_btn-bg: var(--s-surface-sunken);
83
+ }
84
+ }
85
+
86
+ [s-btn="link"] {
87
+ --_btn-bg: transparent;
88
+ --_btn-text: var(--s-interactive-primary);
89
+ --_btn-border: transparent;
90
+ color: var(--_btn-text);
91
+ text-underline-offset: 2px;
92
+ text-decoration: underline;
93
+
94
+ &:hover:not(:disabled) {
95
+ --_btn-text: var(--s-interactive-primary-hover);
96
+ color: var(--_btn-text);
97
+ }
98
+ }
99
+
100
+ [s-btn="outline"] {
101
+ --_btn-bg: transparent;
102
+ --_btn-text: var(--s-interactive-primary);
103
+ --_btn-border: var(--s-interactive-primary);
104
+ color: var(--_btn-text);
105
+
106
+ &:hover:not(:disabled) {
107
+ --_btn-bg: var(--s-interactive-primary);
108
+ --_btn-text: var(--s-text-inverse);
109
+ color: var(--_btn-text);
110
+ }
111
+ }
112
+
113
+ [s-btn="danger"] {
114
+ --_btn-bg: var(--s-state-danger);
115
+
116
+ &:hover:not(:disabled) {
117
+ --_btn-bg: var(--s-danger-800);
118
+ }
119
+
120
+ &:active:not(:disabled) {
121
+ --_btn-bg: var(--s-danger-900);
122
+ }
123
+ }
124
+
125
+ [s-btn="success"] {
126
+ --_btn-bg: var(--s-state-success);
127
+
128
+ &:hover:not(:disabled) {
129
+ --_btn-bg: var(--s-success-800);
130
+ }
131
+
132
+ &:active:not(:disabled) {
133
+ --_btn-bg: var(--s-success-900);
134
+ }
135
+ }
136
+
137
+ [s-btn="warning"] {
138
+ --_btn-bg: var(--s-state-warning);
139
+
140
+ &:hover:not(:disabled) {
141
+ --_btn-bg: var(--s-warning-800);
142
+ }
143
+
144
+ &:active:not(:disabled) {
145
+ --_btn-bg: var(--s-warning-900);
146
+ }
147
+ }
148
+
149
+ [s-btn][s-size="sm"] {
150
+ padding: var(--s-space-1) var(--s-space-3);
151
+ font-size: var(--s-text-xs);
152
+ }
153
+
154
+ [s-btn][s-size="lg"] {
155
+ padding: var(--s-space-3) var(--s-space-6);
156
+ font-size: var(--s-text-base);
157
+ }
158
+
159
+ [s-btn][s-size="xl"] {
160
+ padding: var(--s-space-4) var(--s-space-8);
161
+ font-size: var(--s-text-lg);
162
+ }
163
+
164
+ [s-btn][s-icon] {
165
+ padding: var(--s-space-2);
166
+ aspect-ratio: 1;
167
+ }
168
+
169
+ [s-btn][s-block] {
170
+ width: 100%;
171
+ }
172
+
173
+ [s-btn][s-loading] {
174
+ color: #0000;
175
+ pointer-events: none;
176
+ position: relative;
177
+
178
+ &:after {
179
+ content: "";
180
+ border: 2px solid var(--s-text-inverse);
181
+ border-right-color: #0000;
182
+ border-radius: 50%;
183
+ width: 1em;
184
+ height: 1em;
185
+ margin: auto;
186
+ animation: .6s linear infinite s-btn-spin;
187
+ position: absolute;
188
+ inset: 0;
189
+ }
190
+ }
191
+
192
+ @keyframes s-btn-spin {
193
+ to {
194
+ transform: rotate(360deg);
195
+ }
196
+ }
197
+
198
+ [s-btn-group] {
199
+ display: inline-flex;
200
+
201
+ & > [s-btn] {
202
+ border-radius: 0;
203
+
204
+ &:first-child {
205
+ border-radius: var(--s-radius-md) 0 0 var(--s-radius-md);
206
+ }
207
+
208
+ &:last-child {
209
+ border-radius: 0 var(--s-radius-md) var(--s-radius-md) 0;
210
+ }
211
+
212
+ &:not(:last-child) {
213
+ border-right: none;
214
+ }
215
+ }
216
+ }
@@ -0,0 +1,160 @@
1
+ :where([s-card]) {
2
+ --_card-bg: var(--s-surface-raised);
3
+ --_card-border: var(--s-border-muted);
4
+ --_card-radius: var(--s-radius-xl);
5
+ --_card-shadow: var(--s-shadow-base);
6
+ --_card-padding: var(--s-space-4);
7
+ background-color: var(--_card-bg);
8
+ border: 1px solid var(--_card-border);
9
+ border-radius: var(--_card-radius);
10
+ box-shadow: var(--_card-shadow);
11
+ color: var(--s-text-primary);
12
+ flex-direction: column;
13
+ display: flex;
14
+ overflow: hidden;
15
+ container-type: inline-size;
16
+
17
+ @supports (color: oklch(from red l c h)) {
18
+ color: oklch(from var(--_card-bg) clamp(.15, calc((.55 - l) * 1000 + .15), .95) 0 0);
19
+ }
20
+ }
21
+
22
+ [s-card-header] {
23
+ padding: var(--_card-padding, var(--s-space-4));
24
+ border-bottom: 1px solid var(--_card-border, var(--s-border-muted));
25
+ }
26
+
27
+ [s-card-body] {
28
+ padding: var(--_card-padding, var(--s-space-4));
29
+ flex: 1;
30
+ }
31
+
32
+ [s-card-footer] {
33
+ padding: var(--_card-padding, var(--s-space-4));
34
+ border-top: 1px solid var(--_card-border, var(--s-border-muted));
35
+ }
36
+
37
+ [s-card-media] {
38
+ aspect-ratio: 16 / 9;
39
+ overflow: hidden;
40
+
41
+ & > img {
42
+ object-fit: cover;
43
+ width: 100%;
44
+ height: 100%;
45
+ }
46
+ }
47
+
48
+ [s-card-title] {
49
+ font-size: var(--s-text-lg);
50
+ font-weight: var(--s-font-semibold);
51
+ line-height: var(--s-leading-tight);
52
+ margin-bottom: var(--s-space-1);
53
+ }
54
+
55
+ [s-card-subtitle] {
56
+ font-size: var(--s-text-sm);
57
+ color: var(--s-text-secondary);
58
+ }
59
+
60
+ [s-card="flat"] {
61
+ --_card-shadow: none;
62
+ --_card-border: var(--s-border-default);
63
+ }
64
+
65
+ [s-card="elevated"], [s-card][s-surface="raised"] {
66
+ --_card-shadow: var(--s-shadow-lg);
67
+ --_card-border: transparent;
68
+ }
69
+
70
+ [s-card][s-surface="floating"] {
71
+ --_card-shadow: var(--s-shadow-xl);
72
+ --_card-border: transparent;
73
+ }
74
+
75
+ [s-card="outline"] {
76
+ --_card-shadow: none;
77
+ --_card-border: var(--s-border-strong);
78
+ --_card-bg: transparent;
79
+ }
80
+
81
+ [s-card][s-interactive] {
82
+ cursor: pointer;
83
+ transition: transform var(--s-duration-200) var(--s-ease-out), box-shadow var(--s-duration-200) var(--s-ease-out);
84
+
85
+ &:hover {
86
+ --_card-shadow: var(--s-shadow-xl);
87
+ transform: translateY(-4px);
88
+ }
89
+
90
+ &:active {
91
+ transform: translateY(-2px);
92
+ }
93
+
94
+ &:focus-visible {
95
+ outline: 2px solid var(--s-focus-ring);
96
+ outline-offset: 2px;
97
+ }
98
+ }
99
+
100
+ [s-card][s-link] {
101
+ position: relative;
102
+
103
+ & a:after {
104
+ content: "";
105
+ position: absolute;
106
+ inset: 0;
107
+ }
108
+ }
109
+
110
+ [s-card][s-horizontal] {
111
+ flex-direction: row;
112
+
113
+ & [s-card-media] {
114
+ aspect-ratio: 1;
115
+ flex-shrink: 0;
116
+ width: 33%;
117
+ }
118
+ }
119
+
120
+ @container (width <= 24rem) {
121
+ [s-card][s-horizontal] {
122
+ flex-direction: column;
123
+
124
+ & [s-card-media] {
125
+ aspect-ratio: 16 / 9;
126
+ width: 100%;
127
+ }
128
+ }
129
+ }
130
+
131
+ [s-card][s-size="sm"] {
132
+ --_card-padding: var(--s-space-3);
133
+ --_card-radius: var(--s-radius-lg);
134
+ }
135
+
136
+ [s-card][s-size="lg"] {
137
+ --_card-padding: var(--s-space-6);
138
+ }
139
+
140
+ [s-card="feature"] {
141
+ text-align: center;
142
+ --_card-padding: var(--s-space-6);
143
+ }
144
+
145
+ [s-card-icon] {
146
+ font-size: var(--s-text-4xl);
147
+ margin-bottom: var(--s-space-4);
148
+ }
149
+
150
+ [s-card-grid] {
151
+ gap: var(--s-space-4);
152
+ grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr));
153
+ display: grid;
154
+ }
155
+
156
+ [s-card-stack] {
157
+ gap: var(--s-space-4);
158
+ flex-direction: column;
159
+ display: flex;
160
+ }
@@ -0,0 +1,231 @@
1
+ :where([s-input]) {
2
+ --_input-bg: var(--s-surface-base);
3
+ --_input-border: var(--s-border-default);
4
+ --_input-text: var(--s-text-primary);
5
+ --_input-placeholder: var(--s-text-tertiary);
6
+ --_input-radius: var(--s-radius-md);
7
+ appearance: none;
8
+ width: 100%;
9
+ padding: var(--s-space-2) var(--s-space-3);
10
+ background-color: var(--_input-bg);
11
+ border: 2px solid var(--_input-border);
12
+ border-radius: var(--_input-radius);
13
+ color: var(--_input-text);
14
+ font-size: var(--s-text-sm);
15
+ line-height: var(--s-leading-normal);
16
+ transition: border-color var(--s-duration-150) var(--s-ease-out), box-shadow var(--s-duration-150) var(--s-ease-out);
17
+ display: block;
18
+
19
+ &::placeholder {
20
+ color: var(--_input-placeholder);
21
+ }
22
+
23
+ &:hover:not(:disabled):not(:focus-visible) {
24
+ --_input-border: var(--s-border-strong);
25
+ }
26
+
27
+ &:focus-visible {
28
+ --_input-border: var(--s-interactive-primary);
29
+ outline: 2px solid var(--s-focus-ring);
30
+ outline-offset: 2px;
31
+ }
32
+
33
+ &:disabled {
34
+ opacity: .5;
35
+ cursor: not-allowed;
36
+ background-color: var(--s-surface-sunken);
37
+ }
38
+
39
+ &:invalid:not(:placeholder-shown), &[aria-invalid="true"] {
40
+ --_input-border: var(--s-state-danger);
41
+
42
+ &:focus-visible {
43
+ outline-color: var(--s-state-danger);
44
+ }
45
+ }
46
+ }
47
+
48
+ [s-input][s-size="sm"] {
49
+ padding: var(--s-space-1) var(--s-space-2);
50
+ font-size: var(--s-text-xs);
51
+ }
52
+
53
+ [s-input][s-size="lg"] {
54
+ padding: var(--s-space-3) var(--s-space-4);
55
+ font-size: var(--s-text-base);
56
+ }
57
+
58
+ textarea[s-input] {
59
+ resize: vertical;
60
+ min-height: 6rem;
61
+ }
62
+
63
+ select[s-input] {
64
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
65
+ background-repeat: no-repeat;
66
+ background-position: right var(--s-space-3) center;
67
+ padding-right: var(--s-space-10);
68
+ }
69
+
70
+ [s-input-group] {
71
+ display: flex;
72
+
73
+ & > [s-input] {
74
+ border-radius: 0;
75
+ flex: 1;
76
+ min-width: 0;
77
+
78
+ &:first-child {
79
+ border-radius: var(--s-radius-md) 0 0 var(--s-radius-md);
80
+ }
81
+
82
+ &:last-child {
83
+ border-radius: 0 var(--s-radius-md) var(--s-radius-md) 0;
84
+ }
85
+ }
86
+
87
+ & > [s-input-addon] {
88
+ padding: var(--s-space-2) var(--s-space-3);
89
+ background-color: var(--s-surface-sunken);
90
+ border: 2px solid var(--s-border-default);
91
+ color: var(--s-text-secondary);
92
+ font-size: var(--s-text-sm);
93
+ white-space: nowrap;
94
+ align-items: center;
95
+ display: flex;
96
+
97
+ &:first-child {
98
+ border-radius: var(--s-radius-md) 0 0 var(--s-radius-md);
99
+ border-right: none;
100
+ }
101
+
102
+ &:last-child {
103
+ border-radius: 0 var(--s-radius-md) var(--s-radius-md) 0;
104
+ border-left: none;
105
+ }
106
+ }
107
+ }
108
+
109
+ [s-field] {
110
+ gap: var(--s-space-1);
111
+ flex-direction: column;
112
+ display: flex;
113
+ }
114
+
115
+ [s-field-label] {
116
+ font-size: var(--s-text-sm);
117
+ font-weight: var(--s-font-medium);
118
+ color: var(--s-text-primary);
119
+ }
120
+
121
+ [s-field-hint] {
122
+ font-size: var(--s-text-xs);
123
+ color: var(--s-text-secondary);
124
+ }
125
+
126
+ [s-field-error] {
127
+ font-size: var(--s-text-xs);
128
+ color: var(--s-state-danger);
129
+ }
130
+
131
+ [s-checkbox], [s-radio] {
132
+ align-items: center;
133
+ gap: var(--s-space-2);
134
+ cursor: pointer;
135
+ display: inline-flex;
136
+
137
+ & input {
138
+ appearance: none;
139
+ border: 2px solid var(--s-border-default);
140
+ background-color: var(--s-surface-base);
141
+ cursor: pointer;
142
+ width: 1.25rem;
143
+ height: 1.25rem;
144
+ transition: background-color var(--s-duration-150) var(--s-ease-out), border-color var(--s-duration-150) var(--s-ease-out);
145
+
146
+ &:checked {
147
+ background-color: var(--s-interactive-primary);
148
+ border-color: var(--s-interactive-primary);
149
+ }
150
+
151
+ &:focus-visible {
152
+ outline: 2px solid var(--s-focus-ring);
153
+ outline-offset: 2px;
154
+ }
155
+
156
+ &:disabled {
157
+ opacity: .5;
158
+ cursor: not-allowed;
159
+ }
160
+ }
161
+ }
162
+
163
+ [s-checkbox] input {
164
+ border-radius: var(--s-radius-sm);
165
+
166
+ &:checked {
167
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3'%3E%3Cpath d='M20 6L9 17l-5-5'/%3E%3C/svg%3E");
168
+ background-position: center;
169
+ background-repeat: no-repeat;
170
+ background-size: .75rem;
171
+ }
172
+ }
173
+
174
+ [s-radio] input {
175
+ border-radius: 50%;
176
+
177
+ &:checked {
178
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='white'%3E%3Ccircle cx='12' cy='12' r='5'/%3E%3C/svg%3E");
179
+ background-position: center;
180
+ background-repeat: no-repeat;
181
+ }
182
+ }
183
+
184
+ [s-toggle] {
185
+ align-items: center;
186
+ gap: var(--s-space-2);
187
+ cursor: pointer;
188
+ display: inline-flex;
189
+
190
+ & input {
191
+ appearance: none;
192
+ background-color: var(--s-border-default);
193
+ cursor: pointer;
194
+ width: 2.5rem;
195
+ height: 1.5rem;
196
+ transition: background-color var(--s-duration-200) var(--s-ease-out);
197
+ border-radius: 9999px;
198
+ position: relative;
199
+
200
+ &:after {
201
+ content: "";
202
+ background-color: var(--s-surface-base);
203
+ width: calc(1.5rem - 4px);
204
+ height: calc(1.5rem - 4px);
205
+ box-shadow: var(--s-shadow-sm);
206
+ transition: transform var(--s-duration-200) var(--s-ease-out);
207
+ border-radius: 50%;
208
+ position: absolute;
209
+ top: 2px;
210
+ left: 2px;
211
+ }
212
+
213
+ &:checked {
214
+ background-color: var(--s-interactive-primary);
215
+
216
+ &:after {
217
+ transform: translateX(1rem);
218
+ }
219
+ }
220
+
221
+ &:focus-visible {
222
+ outline: 2px solid var(--s-focus-ring);
223
+ outline-offset: 2px;
224
+ }
225
+
226
+ &:disabled {
227
+ opacity: .5;
228
+ cursor: not-allowed;
229
+ }
230
+ }
231
+ }