diff --git a/addons/rose/addon/components/rose/checkbox.js b/addons/rose/addon/components/rose/checkbox.js
new file mode 100644
index 000000000..c07ab19d7
--- /dev/null
+++ b/addons/rose/addon/components/rose/checkbox.js
@@ -0,0 +1,19 @@
+import Component from '@ember/component';
+import layout from '../../templates/components/rose/checkbox';
+
+/**
+ A simple HTML ` ` element wrapped in ``.
+
+ @example
+
+ Label
+ Error
+ Checked
+ Disabled
+ Disabled + Checked
+
+**/
+
+export default Component.extend({
+ layout,
+});
diff --git a/addons/rose/addon/styles/rose/components/_checkbox.scss b/addons/rose/addon/styles/rose/components/_checkbox.scss
new file mode 100644
index 000000000..376b50e9a
--- /dev/null
+++ b/addons/rose/addon/styles/rose/components/_checkbox.scss
@@ -0,0 +1,26 @@
+@use '../utilities/checkbox';
+
+@include checkbox.checkbox-style(
+ 'default',
+ (
+ color: var(--stark),
+ border-color: var(--ui-border),
+ background-color: var(--subtle),
+ ),
+ (
+ border-color: var(--ui-gray),
+ ),
+ (
+ border-color: var(--info),
+ ),
+ (
+ border-color: var(--failure),
+ ),
+ (
+ border-color: var(--info),
+ ),
+ (
+ border-color: var(--ui-border),
+ background-color: var(--ui-gray-subtler-4),
+ )
+);
diff --git a/addons/rose/addon/styles/rose/components/_index.scss b/addons/rose/addon/styles/rose/components/_index.scss
index dd972080b..2667fb1c7 100644
--- a/addons/rose/addon/styles/rose/components/_index.scss
+++ b/addons/rose/addon/styles/rose/components/_index.scss
@@ -1,2 +1,3 @@
@import 'button';
@import 'form';
+@import 'checkbox';
diff --git a/addons/rose/addon/styles/rose/utilities/_checkbox.scss b/addons/rose/addon/styles/rose/utilities/_checkbox.scss
new file mode 100644
index 000000000..4c91255ed
--- /dev/null
+++ b/addons/rose/addon/styles/rose/utilities/_checkbox.scss
@@ -0,0 +1,130 @@
+@use 'sass:map';
+
+@mixin checkbox {
+ color: var(--stark);
+ margin: 0;
+ padding: 0;
+
+ input[type='checkbox'] {
+ -webkit-appearance: none;
+ appearance: none;
+ }
+
+ input[type='checkbox'] {
+ display: inline-block;
+ position: relative;
+ padding: 0;
+ margin: 0;
+ margin-right: 10px;
+ border: 1px solid;
+ border-radius: 2px;
+ width: 16px;
+ height: 16px;
+ background: var(--subtle);
+ vertical-align: -1px;
+ }
+
+ input[type='checkbox']::before {
+ content: '';
+ position: absolute;
+ top: 4px;
+ left: 1px;
+ border-left: 2px solid;
+ border-bottom: 2px solid;
+ height: 3px;
+ width: 10px;
+ transform: rotate(-45deg);
+ visibility: hidden;
+ border-color: black;
+ }
+
+ input[type='checkbox']:checked::before {
+ visibility: visible;
+ }
+
+ input[type='checkbox']:focus {
+ outline: none;
+ }
+}
+
+@mixin checkbox-style(
+ $name,
+ $default,
+ $hover: &,
+ $focus: &,
+ $error: &,
+ $checked: &,
+ $disabled: &
+) {
+ input[type='checkbox'] {
+ -webkit-appearance: none;
+ appearance: none;
+ }
+
+ .rose-checkbox-#{$name} {
+ @include checkbox;
+ border-color: map.get($default, border-color);
+ color: map.get($default, color);
+
+ & input[type='checkbox'] {
+ border-color: map.get($default, border-color);
+ background-color: map.get($default, background-color);
+ }
+
+ & input[type='checkbox']:hover {
+ @if map.get($hover, 'border-color') {
+ border-color: map.get($hover, 'border-color');
+ }
+ }
+
+ & input[type='checkbox']:focus {
+ @if map.get($focus, 'border-color') {
+ border-color: map.get($focus, 'border-color');
+ }
+ }
+
+ @if $checked {
+ & input[type='checkbox']:checked {
+ @if map.get($checked, 'border-color') {
+ border-color: map.get($checked, 'border-color');
+ }
+ }
+
+ & input[type='checkbox']:checked::before {
+ @if map.get($checked, 'border-color') {
+ border-color: map.get($checked, 'border-color');
+ }
+ }
+ }
+
+ & input[type='checkbox']:disabled {
+ cursor: not-allowed;
+ @if map.get($disabled, 'border-color') {
+ border-color: map.get($disabled, 'border-color');
+ }
+ @if map.get($disabled, 'background-color') {
+ background-color: map.get($disabled, 'background-color');
+ }
+
+ &:checked {
+ @if map.get($disabled, 'border-color') {
+ border-color: map.get($disabled, 'border-color');
+ }
+ }
+
+ &:checked::before {
+ @if map.get($disabled, 'border-color') {
+ border-color: map.get($disabled, 'border-color');
+ }
+ }
+ }
+
+ @if $error {
+ & input[type='checkbox'].error {
+ @if map.get($error, 'border-color') {
+ border-color: map.get($error, 'border-color');
+ }
+ }
+ }
+ }
+}
diff --git a/addons/rose/addon/templates/components/rose/checkbox.hbs b/addons/rose/addon/templates/components/rose/checkbox.hbs
new file mode 100644
index 000000000..f6788646a
--- /dev/null
+++ b/addons/rose/addon/templates/components/rose/checkbox.hbs
@@ -0,0 +1,9 @@
+
+
+ {{yield}}
+
diff --git a/addons/rose/app/components/rose/checkbox.js b/addons/rose/app/components/rose/checkbox.js
new file mode 100644
index 000000000..d3323b60d
--- /dev/null
+++ b/addons/rose/app/components/rose/checkbox.js
@@ -0,0 +1 @@
+export { default } from 'rose/components/rose/checkbox';
diff --git a/addons/rose/app/templates/index.hbs b/addons/rose/app/templates/index.hbs
index 45c285a23..51547ff3c 100644
--- a/addons/rose/app/templates/index.hbs
+++ b/addons/rose/app/templates/index.hbs
@@ -10,7 +10,6 @@ Dummy app index template
Secondary
-
+
+Checkbox
+
+Label
+Error
+Checked
+Disabled
+Disabled + Checked
+Default style
diff --git a/addons/rose/stories/checkbox.stories.js b/addons/rose/stories/checkbox.stories.js
new file mode 100644
index 000000000..d77ee1df6
--- /dev/null
+++ b/addons/rose/stories/checkbox.stories.js
@@ -0,0 +1,26 @@
+import { hbs } from 'ember-cli-htmlbars';
+import { text, boolean, select } from '@storybook/addon-knobs';
+
+export default {
+ title: 'Rose::Checkbox',
+};
+
+export const Normal = () => ({
+ template: hbs`
+
+ {{text}}
+
+ `,
+ context: {
+ text: text('text', 'Checkbox 🛎'),
+ checked: boolean('checked', false),
+ disabled: boolean('disabled', false),
+ error: boolean('error', false),
+ style: select('style', { default: 'default'}, 'default')
+ },
+});
diff --git a/addons/rose/tests/integration/components/rose/checkbox-test.js b/addons/rose/tests/integration/components/rose/checkbox-test.js
new file mode 100644
index 000000000..7ecb3f6fe
--- /dev/null
+++ b/addons/rose/tests/integration/components/rose/checkbox-test.js
@@ -0,0 +1,67 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { render } from '@ember/test-helpers';
+import hbs from 'htmlbars-inline-precompile';
+
+module('Integration | Component | rose/checkbox', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders without label', async function (assert) {
+ await render(hbs` `);
+ assert.equal(this.element.textContent.trim(), '');
+ assert.equal(this.element.querySelector('input').type, 'checkbox');
+ });
+
+ test('it renders a checkbox', async function (assert) {
+ await render(hbs` `);
+ assert.equal(this.element.querySelector('input').type, 'checkbox');
+ });
+
+ test('it renders with label', async function (assert) {
+ await render(hbs`Label `);
+ assert.equal(this.element.textContent.trim(), 'Label');
+ });
+
+ test('it is not checked', async function (assert) {
+ await render(hbs` `);
+ assert.equal(this.element.querySelector('input').checked, false);
+ });
+
+ test('it is not disabled', async function (assert) {
+ await render(hbs` `);
+ assert.equal(this.element.querySelector('input').disabled, false);
+ });
+
+ test('it renders with default style', async function (assert) {
+ await render(hbs` `);
+ assert.equal(this.element.querySelector('label').className, 'rose-checkbox-default');
+ });
+
+ test('it is checked when @checked={{true}}', async function (assert) {
+ await render(hbs` `);
+ assert.equal(this.element.querySelector('input').checked, true);
+ });
+
+ test('it marks error when @error={{true}}', async function (assert) {
+ await render(hbs` `);
+ assert.equal(this.element.querySelector('input').className, 'error');
+ });
+
+ test('it is disabled when @disabled={{true}}', async function (assert) {
+ await render(hbs` `);
+ assert.equal(this.element.querySelector('input').disabled, true);
+ });
+
+ test('it is disabled and checked when @disabled={{true}} and @checked={{true}}', async function (assert) {
+ await render(
+ hbs` `
+ );
+ assert.equal(this.element.querySelector('input').checked, true);
+ assert.equal(this.element.querySelector('input').disabled, true);
+ });
+
+ test('it adds a style class when @style defines it', async function (assert) {
+ await render(hbs` `);
+ assert.equal(this.element.querySelector('label').className, 'rose-checkbox-primary');
+ });
+});