├── test
├── test.scss
├── function
│ ├── _index.scss
│ └── _config.scss
└── sass.test.js
├── scss
├── plugin
│ └── _index.scss
├── spruce-styles.scss
├── config
│ ├── _layout.scss
│ ├── form
│ │ ├── _row.scss
│ │ ├── _file.scss
│ │ ├── _description.scss
│ │ ├── _select.scss
│ │ ├── _index.scss
│ │ ├── _label.scss
│ │ ├── _fieldset.scss
│ │ ├── _range.scss
│ │ ├── _group.scss
│ │ ├── _switch.scss
│ │ ├── _check.scss
│ │ ├── _control.scss
│ │ └── _icon.scss
│ ├── _display.scss
│ ├── _transition.scss
│ ├── _escaping-characters.scss
│ ├── _print.scss
│ ├── _breakpoint.scss
│ ├── _index.scss
│ ├── _spacer.scss
│ ├── _table.scss
│ ├── _setting.scss
│ ├── _generator.scss
│ ├── _button.scss
│ ├── _typography.scss
│ └── _color.scss
├── element
│ ├── _divider.scss
│ ├── _index.scss
│ ├── _accessibility.scss
│ ├── _media.scss
│ ├── _utilities.scss
│ ├── _root.scss
│ ├── _default.scss
│ ├── _table.scss
│ └── _typography.scss
├── function
│ ├── _index.scss
│ ├── _css-variable.scss
│ ├── _setting.scss
│ ├── _config.scss
│ ├── _utilities.scss
│ ├── _font-size.scss
│ ├── _spacer.scss
│ └── _color.scss
├── spruce.scss
├── mixin
│ ├── _index.scss
│ ├── _css-variable.scss
│ ├── _color.scss
│ ├── _transition.scss
│ ├── _breakpoint.scss
│ ├── _font-face.scss
│ ├── _selection.scss
│ ├── _variables.scss
│ ├── _generator.scss
│ ├── _form.scss
│ ├── _layout.scss
│ ├── _button.scss
│ └── _utilities.scss
├── form
│ ├── _row.scss
│ ├── _index.scss
│ ├── _validation.scss
│ ├── _description.scss
│ ├── _label.scss
│ ├── _group-label.scss
│ ├── _fieldset.scss
│ ├── _file.scss
│ ├── _group.scss
│ ├── _range.scss
│ ├── _button.scss
│ ├── _switch.scss
│ ├── _check.scss
│ └── _control.scss
└── print
│ └── _index.scss
├── .browserslistrc
├── .stylelintignore
├── preview
├── src
│ ├── _data
│ │ ├── site.json
│ │ ├── helper.js
│ │ └── navigation.json
│ ├── scss
│ │ ├── layout
│ │ │ ├── _index.scss
│ │ │ ├── _container.scss
│ │ │ └── _main.scss
│ │ ├── config
│ │ │ ├── _styles.scss
│ │ │ ├── _index.scss
│ │ │ ├── _font.scss
│ │ │ ├── _dark-mode.scss
│ │ │ ├── _config.scss
│ │ │ └── _dark-colors.scss
│ │ ├── component
│ │ │ ├── _index.scss
│ │ │ ├── _preview.scss
│ │ │ ├── _navigation.scss
│ │ │ ├── _alert.scss
│ │ │ └── _color.scss
│ │ └── main.scss
│ ├── index.html
│ ├── font
│ │ ├── montserrat-v25-latin-ext_latin-700.woff2
│ │ └── montserrat-v25-latin-ext_latin-regular.woff2
│ ├── component.html
│ ├── js
│ │ ├── theme-mode.js
│ │ └── reading-direction.js
│ ├── function.html
│ ├── _includes
│ │ ├── partial
│ │ │ └── meta.html
│ │ └── layout
│ │ │ └── base.html
│ ├── media.html
│ ├── button.html
│ ├── typography.html
│ ├── mixin.html
│ └── table.html
└── dist
│ ├── font
│ ├── montserrat-v25-latin-ext_latin-700.woff2
│ └── montserrat-v25-latin-ext_latin-regular.woff2
│ ├── js
│ ├── theme-mode.js
│ └── reading-direction.js
│ ├── index copy
│ └── index.html
│ ├── index.html
│ ├── component
│ └── index.html
│ └── function
│ └── index.html
├── .github
├── thumbnail-5.png
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── workflows
│ └── test.yml
├── spruce-logo-dark.svg
└── spruce-logo-light.svg
├── CONTRIBUTING.md
├── .npmignore
├── .eslintrc.json
├── .editorconfig
├── eleventy.config.js
├── .stylelintrc.json
├── LICENSE
├── package.json
├── .gitignore
├── CODE_OF_CONDUCT.md
└── README.md
/test/test.scss:
--------------------------------------------------------------------------------
1 | @forward 'function';
2 |
--------------------------------------------------------------------------------
/test/function/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'config';
2 |
--------------------------------------------------------------------------------
/scss/plugin/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'normalize';
2 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | # Browsers that we support
2 |
3 | defaults
4 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | scss/mixin/_layout.scss
2 | scss/plugin/_normalize.scss
3 |
--------------------------------------------------------------------------------
/preview/src/_data/site.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Spruce CSS Preview"
3 | }
4 |
--------------------------------------------------------------------------------
/preview/src/scss/layout/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'container';
2 | @forward 'main';
3 |
--------------------------------------------------------------------------------
/preview/src/index.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Home"
3 | layout: "layout/base.html"
4 | ---
5 |
--------------------------------------------------------------------------------
/preview/src/scss/config/_styles.scss:
--------------------------------------------------------------------------------
1 | @use 'spruce' as *;
2 |
3 | @include generate-styles;
4 |
--------------------------------------------------------------------------------
/.github/thumbnail-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/conedevelopment/sprucecss/HEAD/.github/thumbnail-5.png
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | [See the documentation](https://sprucecss.com/docs/getting-started/contribution).
4 |
--------------------------------------------------------------------------------
/preview/src/scss/config/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'config';
2 | @forward 'font';
3 | @forward 'styles';
4 | @forward 'dark-mode';
5 |
--------------------------------------------------------------------------------
/preview/src/scss/component/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'color';
2 | @forward 'navigation';
3 | @forward 'alert';
4 | @forward 'preview';
5 |
--------------------------------------------------------------------------------
/scss/spruce-styles.scss:
--------------------------------------------------------------------------------
1 | @use 'spruce' as * with (
2 | $settings: (
3 | 'css-custom-properties': true,
4 | ),
5 | );
6 |
7 | @include generate-styles;
8 |
--------------------------------------------------------------------------------
/preview/dist/font/montserrat-v25-latin-ext_latin-700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/conedevelopment/sprucecss/HEAD/preview/dist/font/montserrat-v25-latin-ext_latin-700.woff2
--------------------------------------------------------------------------------
/preview/src/font/montserrat-v25-latin-ext_latin-700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/conedevelopment/sprucecss/HEAD/preview/src/font/montserrat-v25-latin-ext_latin-700.woff2
--------------------------------------------------------------------------------
/preview/src/font/montserrat-v25-latin-ext_latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/conedevelopment/sprucecss/HEAD/preview/src/font/montserrat-v25-latin-ext_latin-regular.woff2
--------------------------------------------------------------------------------
/preview/dist/font/montserrat-v25-latin-ext_latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/conedevelopment/sprucecss/HEAD/preview/dist/font/montserrat-v25-latin-ext_latin-regular.woff2
--------------------------------------------------------------------------------
/scss/config/_layout.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $layout: () !default;
4 | $layout: map.merge(
5 | (
6 | 'container-inline-size': 84rem,
7 | ),
8 | $layout
9 | );
10 |
--------------------------------------------------------------------------------
/scss/config/form/_row.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $form-row: () !default;
4 | $form-row: map.merge(
5 | (
6 | 'inline-size': 20ch,
7 | ),
8 | $form-row
9 | );
10 |
--------------------------------------------------------------------------------
/preview/src/scss/component/_preview.scss:
--------------------------------------------------------------------------------
1 | @use 'spruce' as *;
2 |
3 | .preview-group {
4 | align-items: start;
5 | display: flex;
6 | flex-wrap: wrap;
7 | gap: spacer('s');
8 | }
9 |
--------------------------------------------------------------------------------
/scss/config/form/_file.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 |
3 | $form-file: () !default;
4 | $form-file: map.merge(
5 | (
6 | 'background': 'primary',
7 | ),
8 | $form-file
9 | );
10 |
--------------------------------------------------------------------------------
/scss/element/_divider.scss:
--------------------------------------------------------------------------------
1 | @use '../function' as *;
2 |
3 | @mixin generate-divider {
4 | hr {
5 | border: 0;
6 | border-block-start: 1px solid color('border');
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/scss/function/_index.scss:
--------------------------------------------------------------------------------
1 | @forward 'color';
2 | @forward 'config';
3 | @forward 'css-variable';
4 | @forward 'font-size';
5 | @forward 'utilities';
6 | @forward 'setting';
7 | @forward 'spacer';
8 |
--------------------------------------------------------------------------------
/preview/src/component.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Component"
3 | layout: "layout/base.html"
4 | ---
5 |
6 |
48 |
49 |
50 |
51 | Copy button
52 |
53 |
54 |
55 |
56 | Copy button
57 |
58 |
Copy button
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/preview/src/scss/config/_dark-colors.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:color';
2 |
3 | $color-black: hsl(206deg 100% 7%);
4 | $color-danger: hsl(0deg 71% 51%);
5 | $color-gray: hsl(0deg 0% 97%);
6 | $color-gray-dark: hsl(0deg 0% 100% / 8%);
7 | $color-primary: hsl(261deg 54% 70%);
8 | $color-secondary: hsl(227deg 92% 55%);
9 | $color-success: hsl(150deg 100% 33%);
10 | $color-white: hsl(0deg 0% 95%);
11 |
12 | $colors: (
13 | 'base': (
14 | 'background': $color-black,
15 | 'blockquote-border': $color-primary,
16 | 'border': $color-gray-dark,
17 | 'code-background': hsl(207deg 64% 18%),
18 | 'code-foreground': $color-white,
19 | 'heading': $color-white,
20 | 'link': $color-primary,
21 | 'link-hover': color.scale($color-primary, $lightness: 20%),
22 | 'mark-background': hsl(50deg 100% 80%),
23 | 'mark-foreground': $color-black,
24 | 'marker': $color-primary,
25 | 'primary': $color-primary,
26 | 'secondary': $color-secondary,
27 | 'strong': $color-white,
28 | 'text': $color-gray,
29 | ),
30 | 'btn': (
31 | 'primary-background': hsl(261deg 52% 59%),
32 | 'primary-background-hover': hsl(261deg 52% 65%),
33 | 'primary-foreground': $color-white,
34 | 'primary-shadow': color.adjust($color-primary, $lightness: -25%),
35 | 'secondary-background': $color-secondary,
36 | 'secondary-background-hover': color.adjust($color-secondary, $lightness: 5%),
37 | 'secondary-foreground': $color-white,
38 | 'secondary-shadow': color.adjust($color-secondary, $lightness: -20%),
39 | ),
40 | 'form': (
41 | 'background': color.scale($color-black, $lightness: 5%),
42 | 'background-disabled': $color-black,
43 | 'border': $color-gray-dark,
44 | 'border-disabled': $color-gray-dark,
45 | 'border-focus': $color-primary,
46 | 'check-background': $color-primary,
47 | 'check-focus-ring': $color-primary,
48 | 'check-foreground': $color-black,
49 | 'group-label-background': color.scale($color-black, $lightness: 2.5%),
50 | 'group-label-foreground': $color-gray,
51 | 'invalid': $color-danger,
52 | 'invalid-focus-ring': color.adjust($color-danger, $alpha: -0.75),
53 | 'label': $color-white,
54 | 'legend': $color-white,
55 | 'placeholder': hsl(0deg 0% 60%),
56 | 'range-thumb-background': $color-primary,
57 | 'range-thumb-focus-ring': $color-primary,
58 | 'range-track-background': $color-gray-dark,
59 | 'ring-focus': color.adjust($color-primary, $alpha: -0.75),
60 | 'select-foreground': hsl(0deg 0% 100%),
61 | 'text': $color-gray,
62 | 'valid': $color-success,
63 | 'valid-focus-ring': color.adjust($color-success, $alpha: -0.75),
64 | ),
65 | 'selection': (
66 | 'background': $color-primary,
67 | 'foreground': $color-white,
68 | ),
69 | 'scrollbar': (
70 | 'thumb-background': hsl(0deg 0% 100% / 15%),
71 | 'thumb-background-hover': hsl(0deg 0% 100% / 25%),
72 | 'track-background': hsl(0deg 0% 100% / 5%),
73 | ),
74 | 'table': (
75 | 'border': $color-gray-dark,
76 | 'caption': $color-gray,
77 | 'heading': $color-white,
78 | 'hover': hsl(0deg 0% 100% / 5%),
79 | 'text': $color-gray,
80 | 'stripe': hsl(0deg 0% 100% / 2.5%),
81 | ),
82 | );
83 |
--------------------------------------------------------------------------------
/scss/mixin/_generator.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../config' as *;
3 | @use '../element' as *;
4 | @use '../plugin' as *;
5 | @use '../form' as *;
6 | @use '../print' as *;
7 | @use 'button' as *;
8 |
9 | /// Generate all the styles.
10 | @mixin generate-styles {
11 | @if map.get($generators, 'content', 'normalize') {
12 | @include generate-normalize;
13 | }
14 |
15 | @if map.get($generators, 'content', 'root') {
16 | @include generate-root;
17 | }
18 |
19 | @if map.get($generators, 'content', 'accessibility') {
20 | @include generate-accessibility;
21 | }
22 |
23 | @if map.get($generators, 'content', 'default') {
24 | @include generate-default;
25 | }
26 |
27 | @if map.get($generators, 'content', 'divider') {
28 | @include generate-divider;
29 | }
30 |
31 | @if map.get($generators, 'content', 'media') {
32 | @include generate-media;
33 | }
34 |
35 | @if map.get($generators, 'content', 'table') {
36 | @include generate-table;
37 | }
38 |
39 | @if map.get($generators, 'content', 'typography') {
40 | @include generate-typography;
41 | }
42 |
43 | @if map.get($generators, 'content', 'utilities') {
44 | @include generate-utilities;
45 | }
46 |
47 | @if map.get($generators, 'content', 'print') {
48 | @include generate-print;
49 | }
50 |
51 | @if map.get($generators, 'form', 'btn') {
52 | @include generate-btn('.btn');
53 |
54 | .btn--primary { @include btn-variant(primary); }
55 | .btn--secondary { @include btn-variant(secondary); }
56 | .btn--outline-primary { @include btn-variant-outline(primary); }
57 | .btn--outline-secondary { @include btn-variant-outline(secondary); }
58 | }
59 |
60 | @if map.get($generators, 'form', 'file-btn') {
61 | @include generate-file-btn(
62 | '.form-file',
63 | '::file-selector-button',
64 | false,
65 | true
66 | );
67 | }
68 |
69 | @if map.get($generators, 'form', 'form-label') {
70 | @include generate-form-label;
71 | }
72 |
73 | @if map.get($generators, 'form', 'form-control') {
74 | @include generate-form-control(
75 | '.form-control',
76 | true,
77 | true,
78 | true
79 | );
80 | }
81 |
82 | @if map.get($generators, 'form', 'form-check') {
83 | @include generate-form-check(
84 | '.form-check',
85 | '.form-check__control',
86 | '.form-check__label',
87 | true
88 | );
89 | }
90 |
91 | @if map.get($generators, 'form', 'form-switch') {
92 | @include generate-form-switch(
93 | '.form-switch',
94 | '.form-switch__control',
95 | '.form-switch__label',
96 | true
97 | );
98 | }
99 |
100 | @if map.get($generators, 'form', 'form-fieldset') {
101 | @include generate-form-fieldset;
102 | }
103 |
104 | @if map.get($generators, 'form', 'form-group-label') {
105 | @include generate-form-group-label;
106 | }
107 |
108 | @if map.get($generators, 'form', 'form-group') {
109 | @include generate-form-group;
110 | }
111 |
112 | @if map.get($generators, 'form', 'form-row') {
113 | @include generate-form-row;
114 | }
115 |
116 | @if map.get($generators, 'form', 'form-feedback') {
117 | @include generate-form-feedback;
118 | }
119 |
120 | @if map.get($generators, 'form', 'form-range') {
121 | @include generate-form-range;
122 | }
123 |
124 | @if map.get($generators, 'form', 'form-description') {
125 | @include generate-form-description;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/scss/element/_typography.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../function' as *;
3 | @use '../mixin' as *;
4 | @use '../config' as *;
5 |
6 | @mixin generate-typography {
7 | html {
8 | -webkit-tap-highlight-color: hsl(0deg 0% 0% / 0%);
9 | }
10 |
11 | body {
12 | font-family: config('font-family-base', $typography);
13 | font-size: config('font-size-base', $typography);
14 | font-weight: config('font-weight-base', $typography);
15 | line-height: config('line-height-base', $typography);
16 | }
17 |
18 | @if setting('hyphens') {
19 | p,
20 | li,
21 | h1,
22 | h2,
23 | h3,
24 | h4,
25 | h5,
26 | h6 {
27 | hyphens: auto;
28 | overflow-wrap: break-word;
29 | }
30 | }
31 |
32 | h1,
33 | h2,
34 | h3,
35 | h4,
36 | h5,
37 | h6 {
38 | color: color('heading');
39 | font-family: config('font-family-heading', $typography);
40 | font-weight: config('font-weight-heading', $typography);
41 | letter-spacing: config('letter-spacing-heading', $typography);
42 | line-height: config('line-height-heading', $typography);
43 | }
44 |
45 | h1 {
46 | font-size: font-size('h1');
47 | }
48 |
49 | h2 {
50 | font-size: font-size('h2');
51 | }
52 |
53 | h3 {
54 | font-size: font-size('h3');
55 | }
56 |
57 | h4 {
58 | font-size: font-size('h4');
59 | }
60 |
61 | h5 {
62 | font-size: font-size('h5');
63 | }
64 |
65 | h6 {
66 | font-size: font-size('h6');
67 | }
68 |
69 | ul,
70 | ol {
71 | list-style-position: inside;
72 | @include layout-stack('xxs');
73 |
74 | li {
75 | list-style-position: outside;
76 |
77 | &::marker {
78 | color: color('marker');
79 | }
80 | }
81 | }
82 |
83 | li > ul,
84 | li > ol {
85 | margin-block-start: spacer('xxs');
86 | }
87 |
88 | dl {
89 | dt {
90 | color: color('heading');
91 | font-weight: bold;
92 | }
93 |
94 | dd {
95 | margin: 0;
96 | }
97 |
98 | dd + dt {
99 | margin-block-start: spacer('s');
100 | }
101 | }
102 |
103 | .quote {
104 | border-inline-start: 0.5rem solid color('blockquote-border');
105 | padding-inline-start: spacer('m');
106 | @include layout-stack('xs');
107 |
108 | blockquote {
109 | border-inline-start: 0;
110 | padding-inline-start: 0;
111 | }
112 |
113 | figcaption {
114 | text-align: start;
115 | }
116 | }
117 |
118 | blockquote {
119 | border-inline-start: 0.5rem solid color('blockquote-border');
120 | margin-inline-start: 0;
121 | padding-inline-start: spacer('m');
122 | @include layout-stack('xs');
123 | }
124 |
125 | abbr[title] {
126 | border-block-end: 1px dotted;
127 | cursor: help;
128 | text-decoration: none;
129 | }
130 |
131 | mark {
132 | background-color: color('mark-background');
133 | border-radius: config('border-radius', $typography);
134 | color: color('mark-foreground');
135 | padding: config('inline-padding', $typography);
136 | }
137 |
138 | code,
139 | kbd,
140 | samp {
141 | background-color: color('code-background');
142 | border-radius: config('border-radius', $typography);
143 | color: color('code-foreground');
144 | padding: config('inline-padding', $typography);
145 | }
146 |
147 | strong {
148 | color: color('strong');
149 | }
150 |
151 | .lead {
152 | font-size: config('font-size-lead', $typography);
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/scss/mixin/_form.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../function' as *;
3 | @use '../config' as *;
4 | @use './variables' as *;
5 | @use './breakpoint' as *;
6 |
7 | /// Generate a from focus ring.
8 | /// @param {string} $type - The type of focus ring (box-shadow, outline).
9 | /// @param {string} $border-color - The border color.
10 | /// @param {string} $ring-color - The ring color.
11 | /// @param {string} $box-shadow-type - The box shadow type (outset, inset).
12 | /// @param {string} $ring-size - The ring width.
13 | /// @param {string} $ring-offset - The ring offset.
14 | /// @return {string} - The generated focus ring.
15 | @mixin focus-ring(
16 | $type: 'box-shadow',
17 | $border-color: null,
18 | $ring-color,
19 | $box-shadow-type: outset,
20 | $ring-size: 2px,
21 | $ring-offset: 2px
22 | ) {
23 | @if $type == 'box-shadow' {
24 | border-color: $border-color;
25 | @if $box-shadow-type == 'inset' {
26 | box-shadow: 0 0 0 $ring-size $ring-color inset;
27 | } @else {
28 | box-shadow: 0 0 0 $ring-size $ring-color;
29 | }
30 | outline: 2px solid transparent;
31 | }
32 |
33 | @if $type == 'outline' {
34 | outline: $ring-size solid $ring-color;
35 | outline-offset: $ring-offset;
36 | }
37 | }
38 |
39 | /// Style field disabled input states.
40 | /// @param {string} $background - The background color.
41 | /// @param {string} $border - The border color.
42 | /// @return {string} - The generated disabled input states.
43 | @mixin field-disabled(
44 | $background,
45 | $border
46 | ) {
47 | background-color: $background;
48 | border-color: $border;
49 | cursor: not-allowed;
50 | }
51 |
52 | /// Get custom icon background for input and select fields.
53 | /// @param {string} $icon - The icon (an SVG in string).
54 | /// @param {string} $color - The color.
55 | /// @return {string} - The generated icon background.
56 | @mixin field-icon(
57 | $icon,
58 | $color
59 | ) {
60 | background-image: url('#{svg-escape(str-replace($icon, "#COLOR#", $color))}');
61 | }
62 |
63 | /// Create a form group stacked layout with custom breakpoint.
64 | /// @param {string} $breakpoint - The breakpoint.
65 | /// @return {string} - The generated form group stacked layout.
66 | @mixin form-group-stacked(
67 | $breakpoint: 'sm',
68 | ) {
69 | @if not map.has-key($breakpoints, $breakpoint) {
70 | @error 'The #{$breakpoint} not exists in the breakpoints map.';
71 | }
72 |
73 | @include generate-variables($form-control, $include: ('border-radius'));
74 | display: flex;
75 | flex-direction: column;
76 |
77 | @include breakpoint($breakpoint) { flex-direction: row; }
78 |
79 | > * {
80 | + * {
81 | border-start-end-radius: 0;
82 | border-start-start-radius: 0;
83 | margin-block-start: -1px;
84 |
85 | @include breakpoint($breakpoint) {
86 | border-end-start-radius: 0;
87 | border-start-end-radius: config('border-radius', $form-control);
88 | margin-block-start: 0;
89 | margin-inline-start: -1px;
90 | }
91 | }
92 |
93 | &:not(:last-child) {
94 | border-end-end-radius: 0;
95 | border-end-start-radius: 0;
96 |
97 | @include breakpoint($breakpoint) {
98 | border-end-end-radius: 0;
99 | border-start-end-radius: 0;
100 | }
101 | }
102 |
103 | @include breakpoint($breakpoint) {
104 | &:first-child {
105 | border-end-start-radius: config('border-radius', $form-control);
106 | }
107 | }
108 |
109 | &:focus {
110 | z-index: 2;
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/preview/src/_includes/layout/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 | {% include "partial/meta.html" %}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | {% if navigation.length > 0 %}
25 |
37 | {% endif %}
38 |
39 | Reading direction
40 |
41 | LTR
42 | RTL
43 |
44 |
45 |
46 | Theme mode
47 |
48 | Light
49 | Dark
50 |
51 |
52 |
53 |
54 |
55 | {{ content | safe }}
56 |
57 |
58 |
59 |
60 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/scss/form/_switch.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../config' as *;
3 | @use '../function' as *;
4 | @use '../mixin' as *;
5 |
6 | @mixin generate-form-switch(
7 | $parent,
8 | $input,
9 | $label,
10 | $has-sizes: false
11 | ) {
12 | #{$parent} {
13 | @include generate-variables($form-switch, ('focus-'));
14 | align-items: config('vertical-alignment', $form-switch);
15 | display: inline-flex;
16 | gap: spacer('xs');
17 |
18 | &--block {
19 | inline-size: 100%;
20 | justify-content: space-between;
21 | }
22 | }
23 |
24 | #{$parent}--vertical-center {
25 | align-items: center;
26 | }
27 |
28 | #{$parent}--vertical-start {
29 | align-items: flex-start;
30 | }
31 |
32 | @if ($has-sizes) {
33 | #{$parent}--sm {
34 | @include generate-variables($form-switch-sm);
35 |
36 | @if not map.get($settings, 'css-custom-properties') {
37 | #{$input} {
38 | font-size: config('font-size', $form-switch-sm);
39 | }
40 | }
41 | }
42 |
43 | #{$parent}--lg {
44 | @include generate-variables($form-switch-lg);
45 |
46 | @if not map.get($settings, 'css-custom-properties') {
47 | #{$input} {
48 | font-size: config('font-size', $form-switch-lg);
49 | }
50 | }
51 | }
52 | }
53 |
54 | @at-root {
55 | #{$input} {
56 | @include field-icon(config('switch', $form-icon, false), color('border', 'form', 'true'));
57 | appearance: none;
58 | background-color: color('background', 'form');
59 | background-position: left center;
60 | background-repeat: no-repeat;
61 | background-size: contain;
62 | block-size: 1em;
63 | border: config('border-width', $form-switch) solid color('border', 'form');
64 | border-radius: 2em;
65 | flex-shrink: 0;
66 | font-size: config('font-size', $form-switch);
67 | inline-size: 2em;
68 | line-height: 1;
69 | margin-block: config('margin-block', $form-switch);
70 | transition-duration: config('duration', $transition);
71 | transition-property: background-position, border, box-shadow;
72 | transition-timing-function: config('timing-function', $transition);
73 |
74 | &:focus-visible {
75 | @include focus-ring(
76 | $type: config('focus-ring-type', $form-check, false),
77 | $border-color: color('switch-background', 'form'),
78 | $ring-color: color('switch-focus-ring', 'form'),
79 | $box-shadow-type: config('focus-ring-box-shadow-type', $form-check, false),
80 | $ring-size: config('focus-ring-size', $form-check, false),
81 | $ring-offset: config('focus-ring-offset', $form-check, false)
82 | );
83 | }
84 |
85 | &:checked {
86 | @include field-icon(config('switch', $form-icon, false), color('switch-foreground', 'form', 'true'));
87 | background-color: color('switch-background', 'form');
88 | background-position: right center;
89 | border-color: color('switch-background', 'form');
90 | }
91 |
92 | &:disabled {
93 | @include field-disabled(
94 | $background: color('background-disabled', 'form'),
95 | $border: color('border-disabled', 'form')
96 | );
97 |
98 | + #{$label} {
99 | opacity: 0.5;
100 | }
101 | }
102 | }
103 |
104 | [dir='rtl'] #{$input} {
105 | background-position: right center;
106 |
107 | &:checked {
108 | background-position: left center;
109 | }
110 | }
111 |
112 | #{$label} {
113 | font-weight: config('font-weight', $form-switch);
114 | line-height: config('line-height', $form-switch);
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/scss/config/_color.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:color';
2 | @use 'sass:map';
3 |
4 | $color-black: hsl(205deg 100% 2%) !default;
5 | $color-danger: hsl(0deg 71% 51%) !default;
6 | $color-gray: hsl(208deg 9% 42%) !default;
7 | $color-gray-light: hsl(215deg 63% 93%) !default;
8 | $color-primary: hsl(262deg 71% 49%) !default;
9 | $color-secondary: hsl(227deg 92% 55%) !default;
10 | $color-success: hsl(150deg 100% 33%) !default;
11 | $color-white: hsl(0deg 0% 100%) !default;
12 |
13 | $colors: () !default;
14 | $colors: map.deep-merge(
15 | (
16 | 'alert': (
17 | 'danger': $color-danger,
18 | 'info': hsl(195deg 100% 42%),
19 | 'success': $color-success,
20 | 'warning': hsl(48deg 89% 55%),
21 | ),
22 | 'base': (
23 | 'background': $color-white,
24 | 'blockquote-border': $color-primary,
25 | 'border': $color-gray-light,
26 | 'code-background': color.change($color-primary, $lightness: 97%),
27 | 'code-foreground': $color-black,
28 | 'heading': $color-black,
29 | 'link': $color-primary,
30 | 'link-hover': color.scale($color-primary, $lightness: -20%),
31 | 'mark-background': hsl(50deg 100% 80%),
32 | 'mark-foreground': $color-black,
33 | 'marker': $color-primary,
34 | 'primary': $color-primary,
35 | 'secondary': $color-secondary,
36 | 'strong': $color-black,
37 | 'text': $color-gray,
38 | ),
39 | 'btn': (
40 | 'primary-background': $color-primary,
41 | 'primary-background-hover': color.adjust($color-primary, $lightness: -10%),
42 | 'primary-foreground': $color-white,
43 | 'primary-shadow': color.adjust($color-primary, $lightness: 35%),
44 | 'secondary-background': $color-secondary,
45 | 'secondary-background-hover': color.adjust($color-secondary, $lightness: -10%),
46 | 'secondary-foreground': $color-white,
47 | 'secondary-shadow': color.adjust($color-secondary, $lightness: 35%),
48 | ),
49 | 'form': (
50 | 'background': $color-white,
51 | 'background-disabled': hsl(0deg 0% 95%),
52 | 'border': hsl(260deg 4% 70%),
53 | 'border-disabled': $color-gray-light,
54 | 'border-focus': $color-primary,
55 | 'check-background': $color-primary,
56 | 'check-focus-ring': $color-primary,
57 | 'check-foreground': $color-white,
58 | 'group-label-background': hsl(210deg 60% 98%),
59 | 'group-label-foreground': $color-gray,
60 | 'invalid': $color-danger,
61 | 'invalid-focus-ring': color.adjust($color-danger, $alpha: -0.75),
62 | 'label': $color-black,
63 | 'legend': $color-black,
64 | 'placeholder': hsl(208deg 7% 40%),
65 | 'range-thumb-background': $color-primary,
66 | 'range-thumb-focus-ring': $color-primary,
67 | 'range-track-background': $color-gray-light,
68 | 'ring-focus': color.adjust($color-primary, $alpha: -0.75),
69 | 'select-foreground': $color-black,
70 | 'switch-background': $color-primary,
71 | 'switch-focus-ring': $color-primary,
72 | 'switch-foreground': $color-white,
73 | 'text': $color-gray,
74 | 'valid': $color-success,
75 | 'valid-focus-ring': color.adjust($color-success, $alpha: -0.75),
76 | ),
77 | 'selection': (
78 | 'foreground': $color-white,
79 | 'background': $color-primary,
80 | ),
81 | 'scrollbar': (
82 | 'thumb-background': hsl(0deg 0% 0% / 15%),
83 | 'thumb-background-hover': hsl(0deg 0% 0% / 25%),
84 | 'track-background': hsl(0deg 0% 0% / 5%),
85 | ),
86 | 'table': (
87 | 'border': $color-gray-light,
88 | 'caption': $color-gray,
89 | 'heading': $color-black,
90 | 'hover': hsl(0deg 0% 0% / 5%),
91 | 'stripe': hsl(0deg 0% 0% / 2.5%),
92 | 'text': $color-gray,
93 | ),
94 | ),
95 | $colors
96 | );
97 |
98 | $dark-colors: () !default;
99 | $dark-colors: map.deep-merge(
100 | (),
101 | $dark-colors
102 | );
103 |
--------------------------------------------------------------------------------
/scss/form/_check.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../config' as *;
3 | @use '../function' as *;
4 | @use '../mixin' as *;
5 |
6 | // Create custom checkbox and radio
7 | @mixin generate-form-check(
8 | $parent,
9 | $input,
10 | $label,
11 | $has-sizes: false
12 | ) {
13 | #{$parent} {
14 | @include generate-variables($form-check, ('focus-'));
15 |
16 | align-items: config('vertical-alignment', $form-check);
17 | display: inline-flex;
18 | gap: spacer('xs');
19 | }
20 |
21 | #{$parent}--vertical-center {
22 | align-items: center;
23 | }
24 |
25 | #{$parent}--vertical-start {
26 | align-items: flex-start;
27 | }
28 |
29 | @if ($has-sizes) {
30 | #{$parent}--sm {
31 | @include generate-variables($form-control-sm);
32 |
33 | #{$input} {
34 | font-size: config('font-size', $form-check-sm);
35 | }
36 | }
37 |
38 | #{$parent}--lg {
39 | @include generate-variables($form-control-lg);
40 |
41 | #{$input} {
42 | font-size: config('font-size', $form-check-lg);
43 | }
44 | }
45 | }
46 |
47 | @at-root {
48 | #{$input} {
49 | appearance: none;
50 | background-color: color('background', 'form');
51 | background-position: center;
52 | background-repeat: no-repeat;
53 | background-size: contain;
54 | block-size: 1em;
55 | border: config('border-width', $form-check) solid color('border', 'form');
56 | flex-shrink: 0;
57 | font-size: config('font-size', $form-check);
58 | font-weight: config('font-weight', $form-check);
59 | inline-size: 1em;
60 | line-height: 1;
61 | margin-block: config('margin-block', $form-check);
62 | transition-duration: config('duration', $transition);
63 | transition-property: border, box-shadow;
64 | transition-timing-function: config('timing-function', $transition);
65 |
66 | &[type='radio'] {
67 | border-radius: 50%;
68 | }
69 |
70 | &[type='checkbox'] {
71 | border-radius: config('border-radius', $form-check);
72 | }
73 |
74 | &:focus-visible {
75 | @include focus-ring(
76 | $type: config('focus-ring-type', $form-check, false),
77 | $border-color: color('check-background', 'form'),
78 | $ring-color: color('check-focus-ring', 'form'),
79 | $box-shadow-type: config('focus-ring-box-shadow-type', $form-check, false),
80 | $ring-size: config('focus-ring-size', $form-check, false),
81 | $ring-offset: config('focus-ring-offset', $form-check, false)
82 | );
83 | }
84 |
85 | &:checked {
86 | background-color: color('check-background', 'form');
87 | border-color: color('check-background', 'form');
88 |
89 | &[type='radio'] {
90 | @include field-icon(config('radio', $form-icon, false), color('check-foreground', 'form', true));
91 | }
92 |
93 | &[type='checkbox'] {
94 | @include field-icon(config('checkbox', $form-icon, false), color('check-foreground', 'form', true));
95 | }
96 | }
97 |
98 | &:indeterminate {
99 | &[type='checkbox'] {
100 | @include field-icon(config('checkbox-indeterminate', $form-icon, false), color('check-foreground', 'form', true));
101 | background-color: color('check-background', 'form');
102 | border-color: color('check-background', 'form');
103 | }
104 | }
105 |
106 | &:disabled,
107 | &.disabled {
108 | @include field-disabled(
109 | $background: color('background-disabled', 'form'),
110 | $border: color('border-disabled', 'form')
111 | );
112 |
113 | + #{$label} {
114 | opacity: 0.5;
115 | }
116 | }
117 | }
118 |
119 | #{$label} {
120 | font-weight: config('font-weight', $form-check);
121 | line-height: config('line-height', $form-check);
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/scss/mixin/_layout.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use 'sass:meta';
3 | @use 'sass:string';
4 | @use 'sass:list';
5 | @use '../function' as *;
6 | @use '../config' as *;
7 |
8 | /// Create center layout.
9 | /// @param {string} $gap - The gap between the container and the content.
10 | /// @param {string} $max-inline-size - The maximum width (inline-size) of the container.
11 | /// @return {mixin} - The centered layout.
12 | @mixin layout-center(
13 | $gap: m,
14 | $max-inline-size: config('container-inline-size', $layout)
15 | ) {
16 | @if map.has-key($spacers, $gap) {
17 | $gap: map.get($spacers, $gap);
18 | }
19 |
20 | inline-size: 100%;
21 | margin-inline: auto;
22 | max-inline-size: $max-inline-size;
23 | padding-inline: $gap;
24 | }
25 |
26 | /// Create stack layout.
27 | /// @param {string} $gap - The gap between the the elements.
28 | /// @param {boolean} $inline-size - Whether it has explicit width (inline-size).
29 | /// @param {string} $align - The horizontal alignment of the elements.
30 | /// @param {boolean} $important - Whether it should use the !important keyword.
31 | /// @return {mixin} - The stacked layout.
32 | @mixin layout-stack(
33 | $gap: 'm',
34 | $inline-size: false,
35 | $align: none,
36 | $important: false
37 | ) {
38 | @if map.has-key($spacers, $gap) {
39 | $gap: map.get($spacers, $gap);
40 | }
41 |
42 | @if $align == left or $align == right {
43 | display: flex;
44 | flex-direction: column;
45 | }
46 |
47 | @if $align == left {
48 | align-items: flex-start;
49 | }
50 |
51 | @if $align == right {
52 | align-items: flex-end;
53 | }
54 |
55 | > * {
56 | margin-block-end: 0;
57 | margin-block-start: 0;
58 |
59 | @if $inline-size and $align == none {
60 | inline-size: 100%;
61 | }
62 | }
63 |
64 | > * + * {
65 | @if $important == true {
66 | margin-block-start: $gap !important;
67 | } @else {
68 | margin-block-start: $gap;
69 | }
70 | }
71 | }
72 |
73 | /// Create grid layout.
74 | /// @param {string} $gap - The gap between the the elements.
75 | /// @param {string} $minimum - The minimum width (inline-size) of the elements.
76 | /// @return {mixin} - The grid layout.
77 | @mixin layout-grid(
78 | $gap: 'm',
79 | $minimum: 12.5rem
80 | ) {
81 | @if meta.type-of($gap) == string and string.index($gap, ':') {
82 | $gap: spacer($gap);
83 | } @else if map.has-key($spacers, $gap) {
84 | $gap: map.get($spacers, $gap);
85 | }
86 |
87 | display: grid;
88 | gap: $gap;
89 |
90 | @supports (inline-size: min(#{$minimum}, 100%)) {
91 | & {
92 | grid-template-columns: repeat(auto-fit, minmax(min(#{$minimum}, 100%), 1fr));
93 | }
94 | }
95 | }
96 |
97 | /// Create sidebar layout.
98 | /// @param {string} $gap - The gap between the the elements.
99 | /// @param {string} $inline-size - The width (flex-basis) of the sidebar.
100 | /// @return {mixin} - The sidebar layout.
101 | @mixin layout-sidebar(
102 | $gap: 'm',
103 | $inline-size: 18.75rem
104 | ) {
105 | @if meta.type-of($gap) == string and string.index($gap, ':') {
106 | $gap: spacer($gap);
107 | } @else if map.has-key($spacers, $gap) {
108 | $gap: map.get($spacers, $gap);
109 | }
110 |
111 | display: flex;
112 | flex-wrap: wrap;
113 | gap: $gap;
114 |
115 | & > :first-child {
116 | flex-basis: $inline-size;
117 | flex-grow: 1;
118 | }
119 |
120 | & > :last-child {
121 | flex-basis: 0;
122 | flex-grow: 999;
123 | min-inline-size: 50%;
124 | }
125 | }
126 |
127 | /// Create instinctive flex layout.
128 | /// @param {string} $gap - The gap between the the elements.
129 | /// @param {string} $inline-size - The width (inline size) of the elements.
130 | /// @return {mixin} - The instinctive flex layout.
131 | @mixin layout-flex(
132 | $gap: 'm',
133 | $inline-size: var(--inline-size)
134 | ) {
135 | @if map.has-key($spacers, $gap) {
136 | $gap: map.get($spacers, $gap);
137 | }
138 |
139 | display: flex;
140 | flex-wrap: wrap;
141 | gap: $gap;
142 |
143 | > * {
144 | flex: 1 1 $inline-size;
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/preview/src/scss/main.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:color';
2 | @use 'sass:math';
3 | @use 'sass:map';
4 |
5 | @forward 'config';
6 | @forward 'layout';
7 | @forward 'component';
8 |
9 | @use 'spruce' as *;
10 |
11 | :root {
12 | @include set-css-variable((
13 | --section-gap: spacer-clamp('xl', 'xxl')
14 | ));
15 | }
16 |
17 | @include generate-form-check(
18 | '.wpcf7-list-item label',
19 | '.wpcf7-list-item input',
20 | '.wpcf7-list-item .wpcf7-list-item-label'
21 | );
22 |
23 | @include generate-table(
24 | 'table',
25 | false,
26 | false
27 | );
28 |
29 | .btn--dark {
30 | @include btn-variant('dark');
31 | }
32 |
33 | .btn--outline-dark {
34 | @include btn-variant-outline('dark');
35 | }
36 |
37 | .btn--custom {
38 | @include btn-variant('custom');
39 | box-shadow: -3px 5px color('custom-foreground', 'btn');
40 | border: 3px solid color('custom-foreground', 'btn');
41 | border-radius: 0;
42 | font-family: config('font-family-cursive', $typography);
43 | }
44 |
45 | .btn--tertiary {
46 | @include btn-variant('tertiary');
47 | }
48 |
49 | .clear-btn {
50 | @include clear-btn;
51 | }
52 |
53 | .section-title {
54 | font-size: font-size('h4');
55 | margin-block: 0 spacer('m');
56 |
57 | * + & {
58 | margin-block-start: spacer('l');
59 | }
60 | }
61 |
62 | .ellipsis-1 {
63 | @include text-ellipsis(1);
64 | }
65 |
66 | .ellipsis-2 {
67 | @include text-ellipsis(2);
68 | }
69 |
70 | .selection-1 {
71 | @include selection('secondary', $is-direct: true);
72 | @include transition;
73 | }
74 |
75 | .selection-2 {
76 | @include selection(#000, $is-direct: true);
77 | }
78 |
79 | .ellipsis-btn {
80 | @include text-ellipsis(1);
81 | max-inline-size: 10ch;
82 | }
83 |
84 | .scrollbar {
85 | max-block-size: 15rem;
86 | overflow: auto;
87 | padding-inline-end: spacer('m');
88 | @include scrollbar;
89 | @include layout-stack;
90 | }
91 |
92 | .custom-heading-size {
93 | font-size: responsive-font-size(4rem, 30, 4vw);
94 | font-family: 'Montserrat', sans-serif;
95 | }
96 |
97 | .custom-link {
98 | @include transition(2s, background-color, linear);
99 |
100 | &:hover {
101 | background-color: aqua;
102 | }
103 | }
104 |
105 | .cleared-list {
106 | @include clear-list;
107 | }
108 |
109 | .card {
110 | @include a11y-card-link('.card__link', true);
111 | border: 1px solid color('border');
112 | border-radius: config('border-radius-lg', $display);
113 | padding: spacer('m');
114 |
115 | &__link {
116 | color: color-value('heading');
117 | font-size: font-size('h3');
118 | font-weight: 700;
119 | text-decoration: none;
120 | }
121 | }
122 |
123 | .break-long-url {
124 | @include word-wrap;
125 | }
126 |
127 | .btn-group {
128 | display: flex;
129 | flex-wrap: wrap;
130 | gap: spacer('s');
131 | }
132 |
133 | .form-group {
134 | &--height-test {
135 | align-items: center;
136 | display: flex;
137 | gap: spacer('xs');
138 | }
139 | }
140 |
141 | .form-group--stacked\:md {
142 | @include form-group-stacked('md');
143 | }
144 |
145 | .ring-wrapper {
146 | align-items: center;
147 | display: flex;
148 | flex-wrap: wrap;
149 | gap: 1rem;
150 | }
151 |
152 | .ring-one,
153 | .ring-two {
154 | --size: 3rem;
155 | @include clear-btn;
156 | block-size: var(--size);
157 | inline-size: var(--size);
158 | }
159 |
160 | .ring-one {
161 | background-color: pink;
162 |
163 | &:focus-visible {
164 | @include short-ring('input');
165 | }
166 | }
167 |
168 | .ring-two {
169 | background-color: rebeccapurple;
170 |
171 | &:focus-visible {
172 | @include short-ring('button', 'secondary');
173 | }
174 | }
175 |
176 | .spacer-wrapper {
177 | align-items: center;
178 | display: flex;
179 | gap: spacer('m');
180 | }
181 |
182 | .spacer {
183 | background-color: red;
184 |
185 | div {
186 | background-color: pink;
187 | block-size: 2rem;
188 | inline-size: 5rem;
189 | }
190 |
191 | &--one {
192 | div {
193 | margin: spacer('m');
194 | }
195 | }
196 |
197 | &--two {
198 | div {
199 | margin: spacer('s');
200 | }
201 | }
202 |
203 | &--three {
204 | div {
205 | margin: spacer('xxl:xl');
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/preview/src/typography.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Typography"
3 | layout: "layout/base.html"
4 | ---
5 |
Morbi dui augue, consequat non pulvinar ac, consequat nec massa. Nulla nec purus vitae enim eleifend laoreet quis vitae nunc. Fusce lacinia nunc eget arcu pulvinar finibus. Nulla et egestas augue. Nulla at nunc vel massa ullamcorper posuere. Donec cursus venenatis dui sed aliquam. Curabitur ultrices, odio ac aliquam mollis, urna felis gravida dolor, id mattis ante mauris eu dui.
This is an anchor link
6 |
Custom heading size
7 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In aliquam nibh in facilisis vestibulum. Pellentesque bibendum lorem risus, ut viverra lectus blandit sit amet. Ut rhoncus a dui ac euismod.
8 |
The quick brown fox jumps over the lazy dog
9 |
The quick brown fox jumps over the lazy dog
10 |
The quick brown fox jumps over the lazy dog
11 |
The quick brown fox jumps over the lazy dog
12 |
The quick brown fox jumps over the lazy dog
13 |
The quick brown fox jumps over the lazy dog
14 |
Curabitur posuere placerat odio, a suscipit velit consectetur nec. Proin tincidunt gravida risus eu commodo. Vivamus tempus enim ac metus finibus vestibulum. Donec ac sagittis quam, in ullamcorper dolor. Vestibulum rhoncus tempor lacus in commodo. Morbi finibus sapien sed tortor interdum, vitae ornare mi accumsan. Vestibulum rutrum facilisis tincidunt.
15 |
16 |
Phasellus vel tortor mi. Vivamus bibendum erat CSS lacus, quis tincidunt urna dictum non. Fusce vel ex feugiat, faucibus lectus sit amet, accumsan lacus. Quisque cursus leo nunc, ut maximus arcu suscipit ut.
17 | Nulla laoreet felis mauris, quis Ctrl + S urna aliquet ac. Ut ultricies eros pharetra, elementum urna non, mollis eros. Proin viverra, ipsum a laoreet laoreet, nunc erat pulvinar quam, vel vehicula enim nibh non velit.
18 |
19 |
20 | Details
21 | Nulla et egestas augue. Nulla at nunc vel massa ullamcorper posuere. Donec cursus venenatis dui sed aliquam. Curabitur ultrices, odio ac aliquam mollis, urna felis gravida dolor, id mattis ante mauris eu dui.
22 |
23 |
Custom link, hey
24 |
1 Infinite Loop, CA 95014 United States
25 |
26 | “Two things are infinite: the universe and human stupidity; and I'm not sure about the universe.”
27 | — Albert Einstein,
28 | Quote Investigator
29 |
30 |
31 |
32 | “Two things are infinite: the universe and human stupidity; and I'm not sure about the universe.”
33 |
34 |
35 | Definition List Title
36 | Definition list division.
37 | Kitchen Sink
38 | Used in expressions to describe work in which all conceivable (and some inconceivable) sources have been mined. In this case, a bunch of markup.
39 | aside
40 | Defines content aside from the page content
41 | blockquote
42 | Defines a section that is quoted from another source
43 |
44 |
45 | Unordered List item one
46 |
47 | Nested list item
48 |
49 | Level 3, item one
50 | Level 3, item two
51 | Level 3, item three
52 | Level 3, item four
53 |
54 |
55 | List item two
56 | List item three
57 | List item four
58 |
59 |
60 | List item two
61 | List item three
62 | List item four
63 |
64 |
65 |
66 | List item one
67 |
68 | List item one
69 |
70 | List item one
71 | List item two
72 | List item three
73 | List item four
74 |
75 |
76 | List item two
77 | List item three
78 | List item four
79 |
80 |
81 | List item two
82 | List item three
83 | List item four
84 |
85 |
Heading level #1
86 |
This is a hidden paragraph.
87 |
--------------------------------------------------------------------------------
/scss/mixin/_button.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:color';
2 | @use 'sass:map';
3 | @use '../function' as *;
4 | @use '../config' as *;
5 | @use 'form' as *;
6 |
7 | /// Generate a button focus ring.
8 | /// @param {string} $type - The type of the button for the color value.
9 | /// @param {boolean} $focus - If the focus ring should be generated.
10 | /// @return {string} - The generated focus ring.
11 | @mixin btn-focus-helper(
12 | $type: 'primary',
13 | $focus: true
14 | ) {
15 | @if $focus {
16 | &:focus-visible {
17 | $ring-color: null;
18 |
19 | @if map.has-key($colors, 'btn', $type + '-focus-ring') {
20 | $ring-color: color($type + '-focus-ring', 'btn');
21 | } @else {
22 | $ring-color: color($type + '-background', 'btn');
23 | }
24 |
25 | @include focus-ring(
26 | $type: map.get($btn, 'focus-ring-type'),
27 | $ring-color: $ring-color,
28 | $box-shadow-type: map.get($btn, 'focus-ring-box-shadow-type'),
29 | $ring-size: map.get($btn, 'focus-ring-size'),
30 | $ring-offset: map.get($btn, 'focus-ring-offset')
31 | );
32 | }
33 | }
34 | }
35 |
36 | /// Generate a button variant.
37 | /// @param {string} $type - The type of the button for the color value.
38 | /// @param {boolean} $focus - If the focus ring should be generated.
39 | /// @return {string} - The generated button variant.
40 | /// @throws {error} - If the color key doesn't exist.
41 | @mixin btn-variant(
42 | $type: 'primary',
43 | $focus: true
44 | ) {
45 | @if not map.has-key($colors, 'btn', $type + '-foreground') or not map.has-key($colors, 'btn', $type + '-background') {
46 | @error 'The #{$type + '-foreground'} or #{$type + '-background'} key name doesn\'t exist under btn at the $colors map.';
47 | }
48 |
49 | @include btn-focus-helper($type, $focus);
50 |
51 | background-color: color($type + '-background', 'btn');
52 | border-color: color($type + '-background', 'btn');
53 | color: color($type + '-foreground', 'btn');
54 |
55 | &:hover {
56 | @if map.has-key($colors, 'btn', $type + '-background-hover') {
57 | background-color: color($type + '-background-hover', 'btn');
58 | border-color: color($type + '-background-hover', 'btn');
59 | } @else {
60 | background-color: color.adjust(color($type + '-background', 'btn', true), $lightness: -10%);
61 | border-color: color.adjust(color($type + '-background', 'btn', true), $lightness: -10%);
62 | }
63 |
64 | @if map.has-key($colors, 'btn', $type + '-foreground-hover') {
65 | color: color($type + '-foreground-hover', 'btn');
66 | } @else {
67 | color: color($type + '-foreground', 'btn');
68 | }
69 | }
70 |
71 | @if map.has-key($colors, 'btn', $type + '-shadow') {
72 | &-shadow {
73 | box-shadow: 0 0.55em 1em -0.2em color($type + '-shadow', 'btn'), 0 0.15em 0.35em -0.185em color($type + '-shadow', 'btn');
74 | }
75 | }
76 | }
77 |
78 | /// Generate a button variant with outline.
79 | /// @param {string} $type - The type of the button for the color value.
80 | /// @param {boolean} $focus - If the focus ring should be generated.
81 | /// @return {string} - The generated button variant with outline.
82 | /// @throws {error} - If the color key doesn't exist.
83 | @mixin btn-variant-outline(
84 | $type: primary,
85 | $focus: true
86 | ) {
87 | @if not map.has-key($colors, 'btn', $type + '-foreground') or not map.has-key($colors, 'btn', $type + '-background') {
88 | @error 'The #{$type + '-foreground'} or #{$type + '-background'} key name doesn\'t exist under btn at the $colors map.';
89 | }
90 |
91 | @if map.has-key($colors, 'btn', $type + '-outline-focus-ring') {
92 | @include btn-focus-helper($type + '-outline', $focus);
93 | } @else {
94 | @include btn-focus-helper($type, $focus);
95 | }
96 |
97 | background-color: transparent;
98 |
99 | @if map.has-key($colors, 'btn', $type + '-outline-border') {
100 | border-color: color($type + '-outline-border', 'btn');
101 | } @else {
102 | border-color: color($type + '-background', 'btn');
103 | }
104 |
105 | @if map.has-key($colors, 'btn', $type + '-outline-foreground') {
106 | color: color($type + '-outline-foreground', 'btn');
107 | } @else {
108 | color: color($type + '-background', 'btn');
109 | }
110 |
111 | &:hover {
112 | @if map.has-key($colors, 'btn', $type + '-outline-background-hover') {
113 | background-color: color($type + '-outline-background-hover', 'btn');
114 | border-color: color($type + '-outline-background-hover', 'btn');
115 | } @else {
116 | background-color: color($type + '-background', 'btn');
117 | border-color: color($type + '-background', 'btn');
118 | }
119 |
120 | @if map.has-key($colors, 'btn', $type + '-outline-foreground-hover') {
121 | color: color($type + '-outline-foreground-hover', 'btn');
122 | } @else {
123 | color: color($type + '-foreground', 'btn');
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/scss/mixin/_utilities.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../function' as *;
3 | @use '../config' as *;
4 | @use 'form' as *;
5 |
6 |
7 | /// Hide something from the screen but keep it visible for assistive technology.
8 | /// @return {mixin} - The visually hidden mixin.
9 | @mixin visually-hidden {
10 | block-size: 1px !important;
11 | border: 0 !important;
12 | clip: rect(0, 0, 0, 0) !important;
13 | inline-size: 1px !important;
14 | margin: -1px !important;
15 | overflow: hidden !important;
16 | padding: 0 !important;
17 | position: absolute !important;
18 | white-space: nowrap !important;
19 | }
20 |
21 | /// Crop text and display an ellipsis with multiline.
22 | /// @param {number} $number-of-lines - The number of lines.
23 | /// @return {mixin} - The text ellipsis mixin.
24 | @mixin text-ellipsis(
25 | $number-of-lines: 1
26 | ) {
27 | overflow: hidden;
28 | text-overflow: ellipsis;
29 |
30 | @if $number-of-lines == 1 {
31 | white-space: nowrap;
32 | } @else {
33 | white-space: inherit;
34 |
35 | @supports (-webkit-line-clamp: $number-of-lines) {
36 | -webkit-box-orient: vertical;
37 | display: -webkit-box;
38 | -webkit-line-clamp: $number-of-lines;
39 | }
40 | }
41 | }
42 |
43 | /// Custom scrollbar.
44 | /// @param {string} $thumb-background-color - The background color of the thumb.
45 | /// @param {string} $thumb-background-color-hover - The background color of the thumb when hovered.
46 | /// @param {string} $track-background-color - The background color of the track.
47 | /// @param {string} $size - The size of the scrollbar.
48 | /// @param {string} $border-radius - The border radius of the scrollbar.
49 | /// @return {mixin} - The scrollbar mixin.
50 | @mixin scrollbar(
51 | $thumb-background-color: color('thumb-background', 'scrollbar'),
52 | $thumb-background-color-hover: color('thumb-background-hover', 'scrollbar'),
53 | $track-background-color: color('track-background', 'scrollbar'),
54 | $size: 0.5rem,
55 | $border-radius: config('border-radius-sm', $display)
56 | ) {
57 | &::-webkit-scrollbar {
58 | block-size: $size;
59 | inline-size: $size;
60 | }
61 |
62 | &::-webkit-scrollbar-thumb {
63 | background: $thumb-background-color;
64 | border-radius: $border-radius;
65 |
66 | &:hover {
67 | background: $thumb-background-color-hover;
68 | }
69 | }
70 |
71 | &::-webkit-scrollbar-track {
72 | background: $track-background-color;
73 | border-radius: $border-radius;
74 | }
75 | }
76 |
77 | /// Clear default button styles.
78 | /// @return {mixin} - The clear button mixin.
79 | @mixin clear-btn {
80 | background: none;
81 | border: 0;
82 | color: inherit;
83 | cursor: pointer;
84 | font: inherit;
85 | outline: inherit;
86 | padding: 0;
87 | }
88 |
89 | // Clear list styles
90 | @mixin clear-list {
91 | list-style: none;
92 | margin: 0;
93 | padding: 0;
94 | }
95 |
96 | /// More accessible card linking.
97 | /// @param {string} $link - The link element's selector.
98 | /// @param {boolean} $at-root - Whether to use @at-root.
99 | /// @return {mixin} - The a11y card link mixin.
100 | @mixin a11y-card-link(
101 | $link,
102 | $at-root: false
103 | ) {
104 | position: relative;
105 |
106 | @if $at-root == true {
107 | @at-root {
108 | #{$link}::before {
109 | content: '';
110 | inset: 0;
111 | position: absolute;
112 | }
113 | }
114 | } @else {
115 | #{$link}::before {
116 | content: '';
117 | inset: 0;
118 | position: absolute;
119 | }
120 | }
121 | }
122 |
123 | /// Break long string.
124 | /// @author Chris Coyier - https://css-tricks.com/snippets/css/prevent-long-urls-from-breaking-out-of-container/
125 | /// @return {mixin} - The word-wrap mixin.
126 | @mixin word-wrap {
127 | hyphens: auto;
128 | overflow-wrap: break-word;
129 | word-break: break-all;
130 | word-break: break-word;
131 | word-wrap: break-word;
132 | }
133 |
134 | /// Generate a focus ring.
135 | /// @param {string} $type - The type of the focus ring.
136 | /// @param {string} $btn-type - The type - hence color - of the button.
137 | /// @return {mixin} - The focus ring mixin.
138 | @mixin short-ring(
139 | $type: 'input',
140 | $btn-type: 'primary'
141 | ) {
142 | @if $type == 'input' {
143 | @include focus-ring(
144 | $type: config('focus-ring-type', $form-control, false),
145 | $border-color: color('border-focus', 'form'),
146 | $ring-color: color('ring-focus', 'form'),
147 | $box-shadow-type: config('focus-ring-box-shadow-type', $form-control, false),
148 | $ring-size: config('focus-ring-size', $form-control, false),
149 | $ring-offset: config('focus-ring-offset', $form-control, false)
150 | );
151 | }
152 |
153 | @if $type == 'button' {
154 | $ring-color: null;
155 |
156 | @if map.has-key($colors, 'btn', $btn-type + '-focus-ring') {
157 | $ring-color: color($btn-type + '-focus-ring', 'btn');
158 | } @else {
159 | $ring-color: color($btn-type + '-background', 'btn');
160 | }
161 |
162 | @include focus-ring(
163 | $type: config('focus-ring-type', $btn, false),
164 | $ring-color: $ring-color,
165 | $box-shadow-type: config('focus-ring-box-shadow-type', $btn, false),
166 | $ring-size: config('focus-ring-size', $btn, false),
167 | $ring-offset: config('focus-ring-offset', $btn, false)
168 | );
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | hello@conedevelopment.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/preview/dist/index copy/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
Home - Spruce CSS Preview
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
38 |
39 | Reading direction
40 |
41 | LTR
42 | RTL
43 |
44 |
45 |
46 | Theme mode
47 |
48 | Light
49 | Dark
50 |
51 |
52 |
53 |
54 |
55 | Variants
56 |
57 | Send form
58 | Send form
59 |
60 | Outline
61 |
62 | Send form
63 | Send form
64 |
65 | Sizes
66 |
67 | Large button
68 | Generic button
69 | Small button
70 |
71 | Disabled
72 |
73 | Save
74 | Save
75 |
76 | Shadow button
77 |
78 | Button with shadow
79 | Button with shadow
80 |
81 | Custom button
82 |
83 | Custom button
84 | Tertiary button
85 |
86 | Custom outline button
87 |
88 | This is the button text
89 | This is the button text
90 |
91 | Clear Button
92 |
93 | Remove button styles
94 |
95 | With Icons
96 |
97 |
98 |
99 |
100 | Copy button
101 |
102 |
103 |
104 |
105 | Copy button
106 |
107 |
Copy button
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | **Spruce CSS is an open-source, lightweight and modernish CSS design system, framework built on Sass. Give your project a solid foundation.**
14 |
15 | [](https://github.com/conedevelopment/sprucecss/releases/latest)
16 | [](https://www.npmjs.com/package/sprucecss)
17 | [](https://github.com/conedevelopment/sprucecss/actions/workflows/test.yml)
18 | [](https://github.com/conedevelopment/sprucecss/blob/main/LICENSE)
19 |
20 |
21 |
22 | ## What is Spruce CSS?
23 |
24 | - It is a Sass-based, small framework that operates with just a few utility classes.
25 | - It takes advantage of the Sass members: variables, mixins, and functions.
26 | - It embraces Sass modules, so it uses @use and namespacing for import.
27 | - Spruce is a good choice if you prefer writing CSS instead of HTML. It uses just a few classic utility classes.
28 | - It is a relatively small (~7kb gzipped) framework with a smaller learning curve. The codebase is small but can add more to any project with the available mixins and functions.
29 | - It is that bunch of code you keep manually carrying from project to project.
30 | - It is themeable. You can create different themes using CSS custom properties like a dark one.
31 | - The generated CSS code is separated from the framework. You can use only the tools (variables, mixins, functions) in your project [without the generated styles](https://sprucecss.com/docs/elements/generators).
32 | - Include just a few components. For UI, we have a separate project named [Spruce UI](/ui/getting-started/introduction), where you can find drop-in components.
33 | - [It comes with dark-mode](https://sprucecss.com/docs/customization/themes) (or any theme mode) support. It uses CSS custom properties, so it isn’t that hard to create a new color theme.
34 | - It doesn’t come with a classical grid system.
35 |
36 | ## How to start with Spruce?
37 |
38 | Firstly, we suggest checking out the documentation, precisely the [installation page](https://sprucecss.com/docs/getting-started/installation).
39 |
40 | There is nothing new if you previously used Sass unless you don’t know the newer [module system](https://sass-lang.com/blog/the-module-system-is-launched).
41 |
42 | We made a [Spruce CSS Eleventy Starter](https://github.com/conedevelopment/sprucecss-eleventy-starter), a boilerplate starter template based on the popular static site generator 11ty. It includes a basic compile setup and, of course, Spruce CSS. You can find more information about it on GitHub.
43 |
44 | ## Documentation
45 |
46 | For the complete documentation, please visit our site at [sprucecss.com](https://sprucecss.com). You can edit it at our [separate repository](https://github.com/conedevelopment/sprucecss-site-eleventy).
47 |
48 | ### Getting Started
49 |
50 | - [Introduction](https://sprucecss.com/docs/getting-started/introduction)
51 | - [Installation](https://sprucecss.com/docs/getting-started/installation)
52 | - [Structure and Code](https://sprucecss.com/docs/getting-started/structure-and-code)
53 | - [Sass](https://sprucecss.com/docs/getting-started/sass)
54 | - [Inclusivity and Accessibility](https://sprucecss.com/docs/getting-started/accessibility)
55 | - [Internationalization](https://sprucecss.com/docs/getting-started/internationalization)
56 | - [Print](https://sprucecss.com/docs/getting-started/print)
57 | - [JS](https://sprucecss.com/docs/getting-started/js)
58 | - [Contribution](https://sprucecss.com/docs/getting-started/contribution)
59 | - [Appendix](https://sprucecss.com/docs/getting-started/appendix)
60 |
61 | ### Customization
62 | - [Variables](https://sprucecss.com/docs/customization/variables/)
63 | - [Settings](https://sprucecss.com/docs/customization/settings)
64 | - [Themes](https://sprucecss.com/docs/customization/themes)
65 |
66 | ### Sass
67 | - [Colors](https://sprucecss.com/docs/sass/colors/)
68 | - [Variables](https://sprucecss.com/docs/sass/variables)
69 | - [Mixins](https://sprucecss.com/docs/sass/mixins)
70 | - [Functions](https://sprucecss.com/docs/sass/functions)
71 |
72 | ### Elements
73 | - [Generators](https://sprucecss.com/docs/elements/generators)
74 | - [Typography](https://sprucecss.com/docs/elements/typography)
75 | - [Tables](https://sprucecss.com/docs/elements/tables)
76 | - [Buttons](https://sprucecss.com/docs/elements/buttons)
77 | - [Forms](https://sprucecss.com/docs/elements/forms)
78 | - [Media](https://sprucecss.com/docs/elements/media)
79 |
80 | ## Components
81 |
82 | This collection of reusable user interfaces aims to help you create more coherently with Spruce CSS.
83 |
84 | ## Templates
85 |
86 | - **[Spruce Docs](https://github.com/conedevelopment/sprucecss-eleventy-documentation-template)** - A simple documentation template made with Eleventy.
87 | - **[Root](https://github.com/conedevelopment/sprucecss-root-admin-template)** - A straightforward administration template with standard views and lots of components.
88 |
89 | ## Contributing
90 |
91 | Thank you for considering contributing to Spruce CSS! The [contribution guide](https://sprucecss.com/docs/getting-started/contribution/) can be found in the documentation.
92 |
--------------------------------------------------------------------------------
/preview/src/mixin.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Mixin"
3 | layout: "layout/base.html"
4 | ---
5 |
6 | short-ring
7 |
8 |
9 |
10 |
11 | scrollbar
12 |
16 | a11y-card-link
17 |
18 | text-ellipsis
19 |
20 |
Multiline ellipsis test Pellentesque quis sapien quis ex tincidunt ornare sit amet eu neque. Fusce ut tempor nisl, vitae semper dui. Fusce sagittis felis orci, ut ultrices justo congue a. Aenean a auctor nisi, eu fringilla erat. Cras id lacinia lorem. Nunc porttitor lorem tortor, sit amet faucibus urna vestibulum ac. Sed facilisis tristique consequat. Fusce condimentum sed enim eu varius. Vestibulum scelerisque sapien vulputate maximus commodo. Fusce dictum erat nec felis convallis elementum. Suspendisse blandit nibh varius, varius lacus quis, commodo orci. Mauris non mollis tellus. Maecenas id consectetur erat, at cursus ipsum.
21 |
Multiline ellipsis test Pellentesque quis sapien quis ex tincidunt ornare sit amet eu neque. Fusce ut tempor nisl, vitae semper dui. Fusce sagittis felis orci, ut ultrices justo congue a. Aenean a auctor nisi, eu fringilla erat. Cras id lacinia lorem. Nunc porttitor lorem tortor, sit amet faucibus urna vestibulum ac. Sed facilisis tristique consequat. Fusce condimentum sed enim eu varius. Vestibulum scelerisque sapien vulputate maximus commodo. Fusce dictum erat nec felis convallis elementum. Suspendisse blandit nibh varius, varius lacus quis, commodo orci. Mauris non mollis tellus. Maecenas id consectetur erat, at cursus ipsum.
22 |
Fusce dictum erat nec felis convallis
23 |
24 | selection
25 |
26 |
Sed facilisis tristique consequat. Fusce condimentum sed enim eu varius. Vestibulum scelerisque sapien vulputate maximus commodo. Fusce dictum erat nec felis convallis elementum. Suspendisse blandit nibh varius, varius lacus quis, commodo orci. Mauris non mollis tellus. Maecenas id consectetur erat, at cursus ipsum.
27 |
SELECTION Mauris non mollis tellus. Maecenas id consectetur erat, at cursus ipsum.
28 |
29 | clear-list
30 |
31 | Cleared UL list
32 | List item two
33 | List item three
34 | List item four
35 |
36 | spacer-clamp
37 | Mauris non mollis tellus. Maecenas id consectetur erat, at cursus ipsum.
38 | word-wrap http://www.reallylong.link/rll/eHljLISQs4/VYnSQH4F8X3sNpWLpziElYnqUVCq8WmbR06lHV7vCttOFydbFBKzBovFUYDMl_SGFCRd3eWTUpIggT5TLgATzFQELX_YUMb4U6ah7UG/7sNSn6XjPWAke0FUy26kfo3ckKU0WtJbyeWNBoliQ4iI7ZIu0lSMD4E_AKRrWT_d27gw3/j/GA/xdi9_3xB3Lq2DcUndrHNPm0_iTVNbvxmodOkGqq3rsQwNU3G5mfWVAgKTTs64NlZ6NA11mQDTLLZQ_X6SCqZh3d8wnG2d2ZwrZ/3OU1hdOiWNKnry0Ml8_X/Oo3EZRHLGpv_mehdAfy4KOzDtsYYdeBfDTMj1QBwvVmh8r9sOqthOgtIE15muoY7dPw_SHWVSo8XoH68tdZr2nqm4NiOAjrchNnaS9LqpLD5Zd09_Tpj0hwgfQFAvlA0_0TQzI9xsV1bxB_zY9Y8a0DeP/_sijH9kCQV0rdKT8Fu6QcJ_MPELWSEmdvVqyBlLyjgWHcyAKzJNuU1CnLf425IOTLpjmAUVvCElISwCCdw1_4Klv4riPll1dfvZjU5QaBx3Vq/EZKK8Z247sL5vuap9pOW04CrkQzsLZF2RRLyZ98C5FWXJP9uD41SfTWR8f9ybpA1YaibXaQgTHgtHaFW/kbbmR3zl3LRhV0HZD9/vY4QyRoi4CHnZpF2wq/nDRLtY3Jy7AiBjUQGMi80UTmzucFY/2sV5VcvLObY7UrbVOlQax/dCjxP0NPAQMLJukneUmvf0C3uzoXm07DxynvusbvVejeexdxU9f5Hdqg1V8yKxYpD9NAOB5dPRBdSoT1uhO1IcVIziwdU0S5Ybmf7fqF06JjxIQQXetOf6w6d4P3BZER1FfD/WA7zj7p/KrqxK6ZMFu_npgHrWTI2qF7bn_nAzti9DyGCFaIyqKSmPMQVMyyF/VWjjF2bauMKliqzInbC9sE3pAf82Mgx570z3ThYHK/FRKT7YxrUWURUb1c2JF3vNs28t/G6c63XX0LTSloOpE1Ci1WNHdwzVKHVa1oz5MaW_1lW_Vb/km231/qu0rlV5hpQCH_0QqkJw7/p1uHBoWQq5TUxeH5RyfBwNI/8XYAa1ybPIGhtXQrnv7VYyG2P4pKkIXMjJkUHz4yAGSMT0C8ZShZHAG_xPmhhwpzXZaNsh4C44u5h/6sfjDD2a7YcfyxsGFd4bqfUqsJcwF7g1GbZeBeA68AsIGcA6ZOa_f1Rnzff858u/2X6yhCn53DSgHwQZIei1xo2OSI6Ia8UjgzW10hMr_fbkOuO64nqKTv9Rzk1cktR1lNnBCBZUaU2I0uUlsp4ELG1073iKab_kkPx0jfxNc6F9abeCp3zrTxu6Foo_bplsKdRUZe25wI_hVpc7PUzIxe/Agaf9/WQwO7azadwWDkJJfAHNruhwH6yVfS5JVR_4DT7CcjdowIV2_bNFmJjCljqn__vBn04FsT6nhCBpFCKV5vmGJtXI935staZS1eSp230RFl3hlCEkUxovrAvXKyPVv1OrndCeylLR8mGkUc/rTnl04YgMlqqflSOFdVIcqqH1JvgrhMFtW/myZ/R06O3dKMxppl2Az8NkU/OmYpcOZ4YrcCsFzpsVu2ybi6sjm_AYgC01zERrEoJ9ATX0BpxSdyjmF6XWZKP3iFcRsgv7otfGqzx1mMSVPBddS/jknevdQNezn3/7Qy/sUVKnS1sPEh0bNrxdJ4r1ovAM2Q1esGHxd7u67TA7iTJty3K5Zbh/poh7000k0ZjTLVhmP1PF2f0m3JjIsRoYJkUsjJNSBrnjflSJJbrjoKzFOdR2Og43phCRKDSRIBkZS6ZQULcYD2ptuQjppoTbcRuQhxsrhrEayABlU0KptmBX_tRrtj0zxzrpTxsiM0n_pzSFS0M4Os5EVkp9ZwqVKC6i7U8dafrpUAgk8VK_4pyQ3MFxk1eu2l_w/aS0yM96
39 |
--------------------------------------------------------------------------------
/preview/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Spruce CSS Preview - Minimal, modern CSS framework
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
138 |
139 |
140 | home
141 |
142 |
143 |
144 |
145 |
146 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/scss/form/_control.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:map';
2 | @use '../function' as *;
3 | @use '../mixin' as *;
4 | @use '../config' as *;
5 |
6 | @mixin generate-form-control(
7 | $selector,
8 | $has-states: false,
9 | $has-sizes: false,
10 | $has-select: true
11 | ) {
12 | #{$selector} {
13 | --webkit-date-line-height: 1.375;
14 | @include generate-variables($form-control, ('focus-'));
15 |
16 | appearance: none;
17 | background-color: color('background', 'form');
18 | border: config('border-width', $form-control) solid color('border', 'form');
19 | border-radius: config('border-radius', $form-control);
20 | box-sizing: border-box;
21 | color: color('text', 'form');
22 | display: block;
23 | font-family: config('font-family', $form-control);
24 | font-size: config('font-size', $form-control);
25 | font-weight: config('font-weight', $form-control);
26 | inline-size: 100%;
27 | line-height: config('line-height', $form-control);
28 | padding: config('padding', $form-control);
29 | transition-duration: config('duration', $transition);
30 | transition-property: border, box-shadow;
31 | transition-timing-function: config('timing-function', $transition);
32 |
33 | &::placeholder {
34 | color: color('placeholder', 'form');
35 | };
36 |
37 | &::-webkit-datetime-edit {
38 | line-height: var(--webkit-date-line-height);
39 | }
40 |
41 | &:focus {
42 | @include focus-ring(
43 | $type: config('focus-ring-type', $form-control, false),
44 | $border-color: color('border-focus', 'form'),
45 | $ring-color: color('ring-focus', 'form'),
46 | $box-shadow-type: config('focus-ring-box-shadow-type', $form-control, false),
47 | $ring-size: config('focus-ring-size', $form-control, false),
48 | $ring-offset: config('focus-ring-offset', $form-control, false)
49 | );
50 | }
51 |
52 | &[type='color'] {
53 | @include generate-variables($form-control-color);
54 | aspect-ratio: config('aspect-ratio', $form-control-color);
55 | block-size: config('block-size', $form-control-color);
56 | inline-size: config('inline-size', $form-control-color);
57 | padding: config('padding', $form-control-color);
58 |
59 | &::-webkit-color-swatch-wrapper {
60 | padding: 0;
61 | }
62 |
63 | &::-moz-color-swatch {
64 | border: 0;
65 | border-radius: config('border-radius', $form-control);
66 | }
67 |
68 | &::-webkit-color-swatch {
69 | border: 0;
70 | border-radius: config('border-radius', $form-control);
71 | }
72 | }
73 |
74 | &[disabled],
75 | &[disabled='true'] {
76 | @include field-disabled(
77 | $background: color('background-disabled', 'form'),
78 | $border: color('border-disabled', 'form')
79 | );
80 | }
81 |
82 | @at-root {
83 | textarea#{$selector} {
84 | block-size: config('textarea-block-size', $form-control);
85 | min-block-size: config('textarea-block-size', $form-control);
86 | resize: vertical;
87 | }
88 | }
89 |
90 | @if ($has-states) {
91 | &--valid,
92 | &--invalid {
93 | background-position: center right config('icon-right-offset', $form-select, false);
94 | background-repeat: no-repeat;
95 | background-size: config('icon-inline-size', $form-select, false) auto;
96 | padding-inline-end: config('padding-inline-end', $form-select, false);
97 |
98 | html[dir='rtl'] & {
99 | background-position: center left config('icon-right-offset', $form-select, false);
100 | }
101 | }
102 |
103 | &--valid {
104 | @include field-icon(config('valid', $form-icon, false), color('success', 'alert', true));
105 | border-color: color('success', 'alert');
106 |
107 | &:focus {
108 | @include focus-ring(
109 | $type: config('focus-ring-type', $form-control, false),
110 | $border-color: color('valid', 'form'),
111 | $ring-color: color('valid-focus-ring', 'form', false),
112 | $box-shadow-type: config('focus-ring-box-shadow-type', $form-control),
113 | $ring-size: config('focus-ring-size', $form-control, false),
114 | $ring-offset: config('focus-ring-offset', $form-control, false)
115 | );
116 | }
117 | }
118 |
119 | &--invalid {
120 | @include field-icon(config('invalid', $form-icon, false), color('danger', 'alert', true));
121 | border-color: color('danger', 'alert');
122 |
123 | &:focus {
124 | @include focus-ring(
125 | $type: config('focus-ring-type', $form-control, false),
126 | $border-color: color('invalid', 'form'),
127 | $ring-color: color('invalid-focus-ring', 'form'),
128 | $box-shadow-type: config('focus-ring-box-shadow-type', $form-control, false),
129 | $ring-size: config('focus-ring-size', $form-control, false),
130 | $ring-offset: config('focus-ring-offset', $form-control, false)
131 | );
132 | }
133 | }
134 | }
135 |
136 | @if ($has-sizes) {
137 | &--sm {
138 | --webkit-date-line-height: 1.36;
139 | @include generate-variables($form-control-sm);
140 |
141 | &[type='color'] {
142 | @include generate-variables($form-control-color-sm);
143 | }
144 |
145 | @if not map.get($settings, 'css-custom-properties') {
146 | font-size: config('font-size', $form-control-sm);
147 | padding: config('padding', $form-control-sm);
148 |
149 | &[type='color'] {
150 | aspect-ratio: config('aspect-ratio', $form-control-color-sm);
151 | block-size: config('block-size', $form-control-color-sm);
152 | inline-size: config('inline-size', $form-control-color-sm);
153 | padding: config('padding', $form-control-color-sm);
154 | }
155 | }
156 | }
157 |
158 | &--lg {
159 | --webkit-date-line-height: 1.387;
160 | @include generate-variables($form-control-lg);
161 |
162 | &[type='color'] {
163 | @include generate-variables($form-control-color-lg);
164 | }
165 |
166 | @if not map.get($settings, 'css-custom-properties') {
167 | font-size: config('font-size', $form-control-lg);
168 | padding: config('padding', $form-control-lg);
169 |
170 | &[type='color'] {
171 | aspect-ratio: config('aspect-ratio', $form-control-color-lg);
172 | height: config('block-size', $form-control-color-lg);
173 | inline-size: config('inline-size', $form-control-color-lg);
174 | padding: config('padding', $form-control-color-lg);
175 | }
176 | }
177 | }
178 | }
179 | }
180 |
181 | @if ($has-select) {
182 | select#{$selector} {
183 | &:not([multiple]):not([size]) {
184 | @include field-icon(config('select', $form-icon, false), color('select-foreground', 'form', true));
185 | background-position: center right config('icon-right-offset', $form-select, false);
186 | background-repeat: no-repeat;
187 | background-size: config('icon-inline-size', $form-select, false) auto;
188 | padding-inline-end: config('padding-inline-end', $form-select, false);
189 |
190 | html[dir='rtl'] & {
191 | background-position: center left config('icon-right-offset', $form-select, false);
192 | }
193 | }
194 | }
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/preview/dist/component/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Component - Spruce CSS Preview
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
138 |
139 |
140 | Hello, this is a danger type alert message
141 |
142 |
143 |
144 |
145 |
146 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/preview/src/table.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Table"
3 | layout: "layout/base.html"
4 | ---
5 | Regular
6 |
7 |
8 | This is a regular sized table.
9 |
10 |
11 | Person
12 | Number
13 | Third Column
14 |
15 |
16 |
17 |
18 | Someone Lastname
19 | 900
20 | Nullam quis risus eget urna mollis ornare vel eu leo.
21 |
22 |
23 | Person Name
24 | 1200
25 | Vestibulum id ligula porta felis euismod semper. Donec ullamcorper nulla non metus auctor fringilla.
26 |
27 |
28 | Another Person
29 | 1500
30 | Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Nullam id dolor id nibh ultricies vehicula ut id elit.
31 |
32 |
33 | Last One
34 | 2800
35 | Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum.
36 |
37 |
38 |
39 |
40 | Rounded
41 |
42 |
43 | This is a regular sized, rounded, borderless table.
44 |
45 |
46 | Person
47 | Number
48 | Third Column
49 |
50 |
51 |
52 |
53 | Someone Lastname
54 | 900
55 | Nullam quis risus eget urna mollis ornare vel eu leo.
56 |
57 |
58 | Person Name
59 | 1200
60 | Vestibulum id ligula porta felis euismod semper. Donec ullamcorper nulla non metus auctor fringilla.
61 |
62 |
63 | Another Person
64 | 1500
65 | Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Nullam id dolor id nibh ultricies vehicula ut id elit.
66 |
67 |
68 | Last One
69 | 2800
70 | Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum.
71 |
72 |
73 |
74 |
75 | Small
76 |
77 |
78 | This is a small sized table.
79 |
80 |
81 | Person
82 | Number
83 | Third Column
84 |
85 |
86 |
87 |
88 | Someone Lastname
89 | 900
90 | Nullam quis risus eget urna mollis ornare vel eu leo.
91 |
92 |
93 | Person Name
94 | 1200
95 | Vestibulum id ligula porta felis euismod semper. Donec ullamcorper nulla non metus auctor fringilla.
96 |
97 |
98 | Another Person
99 | 1500
100 | Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Nullam id dolor id nibh ultricies vehicula ut id elit.
101 |
102 |
103 | Last One
104 | 2800
105 | Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum.
106 |
107 |
108 |
109 |
110 | In Line
111 |
112 |
113 |
114 |
115 | Person
116 | Number
117 | Third Column
118 |
119 |
120 |
121 |
122 | Someone Lastname
123 | 900
124 | Nullam quis risus eget urna mollis ornare vel eu leo.
125 |
126 |
127 | Person Name
128 | 1200
129 | Vestibulum id ligula porta felis euismod semper. Donec ullamcorper nulla non metus auctor fringilla.
130 |
131 |
132 | Another Person
133 | 1500
134 | Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Nullam id dolor id nibh ultricies vehicula ut id elit.
135 |
136 |
137 | Last One
138 | 2800
139 | Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum.
140 |
141 |
142 |
143 |
144 | Classless
145 |
146 |
147 |
148 |
149 | Person
150 | Number
151 | Third Column
152 |
153 |
154 |
155 |
156 | Someone Lastname
157 | 900
158 | Nullam quis risus eget urna mollis ornare vel eu leo.
159 |
160 |
161 | Person Name
162 | 1200
163 | Vestibulum id ligula porta felis euismod semper. Donec ullamcorper nulla non metus auctor fringilla.
164 |
165 |
166 | Another Person
167 | 1500
168 | Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Nullam id dolor id nibh ultricies vehicula ut id elit.
169 |
170 |
171 | Last One
172 | 2800
173 | Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum.
174 |
175 |
176 |
177 |
178 |
--------------------------------------------------------------------------------
/preview/dist/function/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Function - Spruce CSS Preview
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
138 |
139 |
140 | spacer
141 |
152 |
153 |
154 |
155 |
156 |
157 |
165 |
166 |
167 |
168 |
169 |
--------------------------------------------------------------------------------
/.github/spruce-logo-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.github/spruce-logo-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------