diff --git a/commitlint.config.js b/commitlint.config.js
index d425a0ae..ac868546 100644
--- a/commitlint.config.js
+++ b/commitlint.config.js
@@ -15,6 +15,7 @@ module.exports = {
'tab',
'tooltip',
'progress-indicator',
+ 'checkbox',
'alert'
],
],
diff --git a/src/baklava.ts b/src/baklava.ts
index fa334537..2a857d82 100644
--- a/src/baklava.ts
+++ b/src/baklava.ts
@@ -7,5 +7,6 @@ export { default as BlTabGroup } from './components/tab-group/bl-tab-group';
export { default as BlTabPanel } from './components/tab-group/tab-panel/bl-tab-panel';
export { default as BlTooltip } from './components/tooltip/bl-tooltip';
export { default as BlProgressIndicator } from './components/progress-indicator/bl-progress-indicator';
+export { default as BlCheckbox } from './components/checkbox/bl-checkbox';
export { default as BlAlert } from './components/alert/bl-alert';
export { getIconPath, setIconPath } from './utilities/asset-paths';
diff --git a/src/components/checkbox/bl-checkbox.css b/src/components/checkbox/bl-checkbox.css
new file mode 100644
index 00000000..24ca9eb2
--- /dev/null
+++ b/src/components/checkbox/bl-checkbox.css
@@ -0,0 +1,50 @@
+:host {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+label {
+ display: flex;
+ align-items: center;
+ gap: var(--bl-size-2xs);
+ color: var(--bl-color-secondary);
+ font: var(--bl-font-title-3);
+ cursor: pointer;
+ user-select: none;
+}
+
+input {
+ appearance: none;
+ position: absolute;
+}
+
+.check-mark {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ width: var(--bl-size-m);
+ height: var(--bl-size-m);
+ border: 1px solid var(--bl-color-border);
+ border-radius: var(--bl-border-radius-xs);
+ color: var(--bl-color-primary-background);
+ font-size: var(--bl-font-size-2xs);
+}
+
+:host([checked]) .label {
+ color: var(--bl-color-primary);
+}
+
+:host(:is([checked], [indeterminate])) .check-mark {
+ background-color: var(--bl-color-primary);
+ border: none;
+}
+
+:host([disabled]) .check-mark,
+:host([disabled]) .label {
+ color: var(--bl-color-content-passive);
+}
+
+:host([disabled]) .check-mark {
+ background-color: var(--bl-color-secondary-background);
+}
diff --git a/src/components/checkbox/bl-checkbox.stories.mdx b/src/components/checkbox/bl-checkbox.stories.mdx
new file mode 100644
index 00000000..f602bb61
--- /dev/null
+++ b/src/components/checkbox/bl-checkbox.stories.mdx
@@ -0,0 +1,106 @@
+import { html } from 'lit';
+import { ifDefined } from 'lit/directives/if-defined.js';
+import { Meta, Canvas, ArgsTable, Story } from '@storybook/addon-docs';
+
+
+
+export const CheckboxTemplate = (args) => html`
+${args.label}
+`;
+
+# Checkbox
+Checkbox component can be used to control checked / unchecked statuses.
+
+### Usage
+
+Use checkbox component for getting true/false input from users.
+
+* Don't use checkbox as an action button.
+* Checkbox label is not required but if you want to use checbox without a label, set `aria-label` attribute.
+
+## Basic
+
+You can show label by just using slot.
+
+
+
+## Checked
+
+Checked state can be set via `checked` attribute.
+
+
+
+## Indeterminate
+
+Indeterminate state is regardless with `checked` state. A checkbox can be both `checked` and `indeterminate` at the
+same time.Indeterminate state is mainly used for parent/child selections while parent checkbox represents if all of
+the child will/should be checked or not. User interaction with checkbox (if checkbox is not disabled) takes checkbox
+from `indeterminate` state. Checkbox doesn't go this state with a user interaction.
+
+
+
+## Indeterminate And Checked
+
+Indeterminate state is regardless with `checked` state. A checkbox cannot be both `checked` and `indeterminate` at the
+same time. Unless there is a user interaction, when `indeterminate` state is active `checked` state changes ignored.
+
+
+
+## Disabled
+
+Disabled state can be set via `disabled` attribute. A checkbox can be `disabled` and `checked` (and even `indeterminate`) at the same time.
+
+
+
+## Reference
+
+
diff --git a/src/components/checkbox/bl-checkbox.test.ts b/src/components/checkbox/bl-checkbox.test.ts
new file mode 100644
index 00000000..ec1b80d0
--- /dev/null
+++ b/src/components/checkbox/bl-checkbox.test.ts
@@ -0,0 +1,104 @@
+import { assert, fixture, html, elementUpdated, expect, oneEvent } from '@open-wc/testing';
+import BlCheckbox from './bl-checkbox';
+
+describe('bl-checkbox', () => {
+ it('should be defined checkbox instance', () => {
+ const el = document.createElement('bl-checkbox');
+ assert.instanceOf(el, BlCheckbox);
+ });
+
+ it('should be rendered with default values', async () => {
+ const el = await fixture(html``);
+
+ assert.shadowDom.equal(
+ el,
+ `
+
+ `
+ );
+ });
+
+ it('should be rendered with correct label attribute', async () => {
+ const el = await fixture(html``);
+
+ expect(el.shadowRoot?.querySelector('span')).to.exist;
+ expect(el.getAttribute('label')).to.eq('test label');
+ });
+
+ it('should be rendered with correct label attribute when label attribute was changed', async () => {
+ const el = await fixture(html``);
+
+ el.setAttribute('label', 'new test label');
+
+ await elementUpdated(el);
+
+ expect(el.getAttribute('label')).to.eq('new test label');
+ });
+
+ it('should be rendered with check icon when checkbox checked', async () => {
+ const el = await fixture(html``);
+ const iconEl = el.shadowRoot?.querySelector('bl-icon');
+
+ expect(iconEl?.getAttribute('name')).to.eq('check');
+ });
+
+ it('should render with `checked` attribute as checked value', async () => {
+ const el = await fixture(html``);
+ expect(el.shadowRoot?.querySelector('input')?.checked).to.eq(true);
+ });
+
+ describe('attributes', () => {
+ it('should render with `disabled` attribute as disabled', async () => {
+ const el = await fixture(html``);
+ expect(el.shadowRoot?.querySelector('input')?.hasAttribute('disabled')).to.eq(true);
+ });
+
+ it('should not render with `indeterminate` attribute as indeterminate', async () => {
+ const el = await fixture(html``);
+ expect(el.shadowRoot?.querySelector('input')?.hasAttribute('indeterminate')).to.eq(false);
+ });
+ });
+
+ describe('update', () => {
+ it('should set checked to false when indeterminate set to true', async () => {
+ const el = await fixture(html``);
+
+ el.setAttribute('indeterminate', 'true');
+ await elementUpdated(el);
+
+ expect(el.hasAttribute('checked')).to.eq(false);
+ });
+ it('should set checked to false when indeterminate and checked set to true at start', async () => {
+ const el = await fixture(html``);
+ expect(el.hasAttribute('checked')).to.eq(false);
+ });
+ });
+
+ describe('events', () => {
+ it('should fire bl-checkbox-change event with detail is true when checkbox is unchecked', async () => {
+ const el = await fixture(html``);
+ const checkbox = el.shadowRoot?.querySelector('input');
+
+ setTimeout(() => checkbox?.click());
+ const ev = await oneEvent(el, 'bl-checkbox-change');
+
+ expect(ev).to.exist;
+ expect(ev.detail).to.be.equal(true);
+ });
+
+ it('should fire bl-checkbox-change event with detail is false when checkbox is checked', async () => {
+ const el = await fixture(html``);
+ const checkbox = el.shadowRoot?.querySelector('input');
+
+ setTimeout(() => checkbox?.click());
+ const ev = await oneEvent(el, 'bl-checkbox-change');
+
+ expect(ev).to.exist;
+ expect(ev.detail).to.be.equal(false);
+ });
+ });
+});
diff --git a/src/components/checkbox/bl-checkbox.ts b/src/components/checkbox/bl-checkbox.ts
new file mode 100644
index 00000000..2866dddd
--- /dev/null
+++ b/src/components/checkbox/bl-checkbox.ts
@@ -0,0 +1,82 @@
+import { CSSResultGroup, html, LitElement, TemplateResult } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+import { live } from 'lit/directives/live.js';
+import { event, EventDispatcher } from '../../utilities/event';
+import '../icon/bl-icon';
+import style from './bl-checkbox.css';
+
+/**
+ * @tag bl-checkbox
+ * @summary Baklava Checkbox component
+ */
+@customElement('bl-checkbox')
+export default class BlCheckbox extends LitElement {
+ static get styles(): CSSResultGroup {
+ return [style];
+ }
+
+ /**
+ * Sets the checked state for checkbox
+ */
+ @property({ type: Boolean, reflect: true })
+ checked = false;
+
+ /**
+ * Sets the disabled state for checkbox
+ */
+ @property({ type: Boolean, reflect: true })
+ disabled = false;
+
+ /**
+ * Sets the indeterminate state for checkbox
+ */
+ @property({ type: Boolean, reflect: true })
+ indeterminate = false;
+
+ /**
+ * Fires whenever user change the value of the checkbox.
+ */
+ @event('bl-checkbox-change') private onChange: EventDispatcher;
+
+ handleChange(event: CustomEvent) {
+ const target = event.target as HTMLInputElement;
+ this.checked = target.checked;
+ this.onChange(target.checked);
+ this.indeterminate = false;
+ }
+
+ update(changedProperties: Map) {
+ super.update(changedProperties);
+ if (this.indeterminate && this.checked) {
+ this.checked = false;
+ this.requestUpdate('checked', true);
+ }
+ }
+
+ render(): TemplateResult {
+ let icon = '';
+ if (this.checked) icon = 'check';
+ if (this.indeterminate) icon = 'minus';
+
+ return html`
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'bl-checkbox': BlCheckbox;
+ }
+}