diff --git a/addons/rose/addon/components/rose/form/checkbox.js b/addons/rose/addon/components/rose/form/checkbox.js
new file mode 100644
index 000000000..5b94c0a1d
--- /dev/null
+++ b/addons/rose/addon/components/rose/form/checkbox.js
@@ -0,0 +1,21 @@
+import Component from '@ember/component';
+import layout from '../../../templates/components/rose/form/checkbox';
+
+/**
+ * A simple HTML `` element.
+ *
+ * @example
+ *
+ */
+export default Component.extend({
+ layout,
+
+ tagName: '',
+});
diff --git a/addons/rose/addon/styles/rose/components/form/_checkbox.scss b/addons/rose/addon/styles/rose/components/form/_checkbox.scss
new file mode 100644
index 000000000..4b826841e
--- /dev/null
+++ b/addons/rose/addon/styles/rose/components/form/_checkbox.scss
@@ -0,0 +1,122 @@
+@use '../../variables/sizing';
+@use '../../utilities/type';
+
+.rose-form-checkbox {
+ position: relative;
+ margin-bottom: sizing.rems(xs);
+ color: var(--ui-gray);
+
+ .rose-form-checkbox-label {
+ // label styles
+ @include type.type(s, semibold);
+ color: currentColor;
+ padding-left: sizing.rems(l);
+
+ // custom checkbox styles
+ &::before {
+ position: absolute;
+ top: sizing.rems(xxxs);
+ left: 0;
+ display: block;
+ width: sizing.rems(m);
+ height: sizing.rems(m);
+ pointer-events: none;
+ content: ' ';
+ border: sizing.rems(xxxxs) solid var(--ui-gray-subtler-2);
+ border-radius: sizing.rems(xxxs);
+ }
+
+ &::after {
+ position: absolute;
+ top: -#{sizing.rems(xxxxs)};
+ left: -#{sizing.rems(xxxs) + sizing.rems(xxxxs)};
+ display: block;
+ width: sizing.rems(l);
+ height: sizing.rems(l);
+ transform: scale(0.9);
+ pointer-events: none;
+ content: ' ';
+ background: no-repeat 50%/50% 50%;
+ }
+ }
+
+ .rose-form-checkbox-field {
+ // mostly this just visually hides the native checkbox
+ position: absolute;
+ left: 0;
+ z-index: -1;
+ width: sizing.rems(m);
+ height: sizing.rems(m);
+ opacity: 0;
+
+ // Visual states for custom checkbox.
+ // Yeah. It's complicated.
+ &:not(:disabled) {
+ &:hover {
+ ~ .rose-form-checkbox-label {
+ &::before {
+ border-color: var(--ui-gray);
+ }
+ }
+ }
+
+ &:focus {
+ ~ .rose-form-checkbox-label {
+ &::before {
+ border-color: var(--info);
+ }
+ }
+ }
+
+ &:checked {
+ ~ .rose-form-checkbox-label {
+ &::before {
+ border-color: var(--info);
+ }
+ &::after {
+ background-image: url("data:image/svg+xml,");
+ }
+ }
+ }
+ }
+
+ &:disabled {
+ ~ .rose-form-checkbox-label {
+ &::before {
+ border-color: var(--ui-gray-subtler-2);
+ background-color: var(--ui-gray-subtler-4);
+ }
+ }
+
+ &:checked {
+ ~ .rose-form-checkbox-label {
+ &::after {
+ background-image: url("data:image/svg+xml,");
+ }
+ }
+ }
+ }
+ }
+
+ &.error {
+ color: var(--failure);
+
+ .rose-form-checkbox-field {
+ &:not(:disabled):not(:focus) {
+ ~ .rose-form-checkbox-label {
+ &::before {
+ border-color: var(--failure);
+ }
+ }
+
+ &:checked {
+ ~ .rose-form-checkbox-label {
+ &::after {
+ background-image: url("data:image/svg+xml,");
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/addons/rose/addon/styles/rose/components/form/_index.scss b/addons/rose/addon/styles/rose/components/form/_index.scss
index fdc60b1cd..55ce75517 100644
--- a/addons/rose/addon/styles/rose/components/form/_index.scss
+++ b/addons/rose/addon/styles/rose/components/form/_index.scss
@@ -1 +1,2 @@
@import 'input';
+@import 'checkbox';
diff --git a/addons/rose/addon/styles/rose/variables/_sizing.scss b/addons/rose/addon/styles/rose/variables/_sizing.scss
index 20fed7233..81b9c94e9 100644
--- a/addons/rose/addon/styles/rose/variables/_sizing.scss
+++ b/addons/rose/addon/styles/rose/variables/_sizing.scss
@@ -23,7 +23,8 @@ $size-coefficients: (
// 4
xxxs: 0.125,
// 2
- xxxxs: 0.0625 // 1,,,,,
+ xxxxs: 0.0625,
+ // 1
);
/**
diff --git a/addons/rose/addon/styles/rose/variables/_typography.scss b/addons/rose/addon/styles/rose/variables/_typography.scss
index 71111ac26..067c74496 100644
--- a/addons/rose/addon/styles/rose/variables/_typography.scss
+++ b/addons/rose/addon/styles/rose/variables/_typography.scss
@@ -40,7 +40,8 @@ $font-sizes: (
// 20
h3: sizing.size(m),
// 16
- button: sizing.size(m) * 0.8125 // 13,,,,,
+ button: sizing.size(m) * 0.8125,
+ // 13
);
// Line height expressed as ratio.
diff --git a/addons/rose/addon/templates/components/rose/form/checkbox.hbs b/addons/rose/addon/templates/components/rose/form/checkbox.hbs
new file mode 100644
index 000000000..ab2dec73b
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/form/checkbox.hbs
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ {{yield}}
+
+
diff --git a/addons/rose/app/components/rose/form/checkbox.js b/addons/rose/app/components/rose/form/checkbox.js
new file mode 100644
index 000000000..fecc9b373
--- /dev/null
+++ b/addons/rose/app/components/rose/form/checkbox.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/form/checkbox';
diff --git a/addons/rose/app/templates/index.hbs b/addons/rose/app/templates/index.hbs
index af9c16fd8..4b4e197a0 100644
--- a/addons/rose/app/templates/index.hbs
+++ b/addons/rose/app/templates/index.hbs
@@ -1,7 +1,9 @@
Dummy app index template
-
+Icon
+
+Button
Primary
Primary
@@ -12,7 +14,7 @@ Dummy app index template
Secondary
-
+Form
+
+Checkbox
+
+
+
+
+
diff --git a/addons/rose/stories/form/checkbox.stories.js b/addons/rose/stories/form/checkbox.stories.js
new file mode 100644
index 000000000..2292dd816
--- /dev/null
+++ b/addons/rose/stories/form/checkbox.stories.js
@@ -0,0 +1,27 @@
+import { hbs } from 'ember-cli-htmlbars';
+import { text, boolean } from '@storybook/addon-knobs';
+
+export default {
+ title: 'Rose::Form::Checkbox',
+};
+
+export const Normal = () => ({
+ template: hbs`
+
+ `,
+ context: {
+ label: text('text', 'Checkbox 🛎'),
+ id: text('id', 'checkbox-1'),
+ name: text('name', 'checkbox-1'),
+ checked: boolean('checked', false),
+ error: boolean('error', false),
+ disabled: boolean('disabled', false),
+ },
+});
diff --git a/addons/rose/tests/integration/components/rose/form/checkbox-test.js b/addons/rose/tests/integration/components/rose/form/checkbox-test.js
new file mode 100644
index 000000000..e341dd0db
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/form/checkbox-test.js
@@ -0,0 +1,39 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render, find } from '@ember/test-helpers';
+import hbs from 'htmlbars-inline-precompile';
+
+module('Integration | Component | rose/form/checkbox', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ await render(hbs``);
+ assert.equal(await find('label').textContent.trim(), 'Label');
+ assert.ok(await find('input'));
+ });
+
+ test('it is not checked by default', async function (assert) {
+ await render(hbs``);
+ assert.equal(await find('input').checked, false);
+ });
+
+ test('it is not disabled by default', async function (assert) {
+ await render(hbs``);
+ assert.equal(await find('input').disabled, false);
+ });
+
+ test('it is checked when @checked={{true}}', async function (assert) {
+ await render(hbs``);
+ assert.equal(await find('input').checked, true);
+ });
+
+ test('it marks error when @error={{true}}', async function (assert) {
+ await render(hbs``);
+ assert.ok(await find('.error'));
+ });
+
+ test('it is disabled when disabled={{true}}', async function (assert) {
+ await render(hbs``);
+ assert.equal(await find('input').disabled, true);
+ });
+});