diff --git a/lib/phlexy_ui/kbd.rb b/lib/phlexy_ui/kbd.rb
new file mode 100644
index 0000000..8406280
--- /dev/null
+++ b/lib/phlexy_ui/kbd.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+module PhlexyUI
+ # @component html class="kbd"
+ class Kbd < Base
+ def initialize(*, as: :kbd, **)
+ super(*, **)
+ @as = as
+ end
+
+ def view_template(&)
+ generate_classes!(
+ # "kbd"
+ component_html_class: :kbd,
+ modifiers_map: modifiers,
+ base_modifiers:,
+ options:
+ ).then do |classes|
+ public_send(as, class: classes, **options, &)
+ end
+ end
+
+ register_modifiers(
+ # "sm:kbd-xs"
+ # "@sm:kbd-xs"
+ # "md:kbd-xs"
+ # "@md:kbd-xs"
+ # "lg:kbd-xs"
+ # "@lg:kbd-xs"
+ xs: "kbd-xs",
+ # "sm:kbd-sm"
+ # "@sm:kbd-sm"
+ # "md:kbd-sm"
+ # "@md:kbd-sm"
+ # "lg:kbd-sm"
+ # "@lg:kbd-sm"
+ sm: "kbd-sm",
+ # "sm:kbd-md"
+ # "@sm:kbd-md"
+ # "md:kbd-md"
+ # "@md:kbd-md"
+ # "lg:kbd-md"
+ # "@lg:kbd-md"
+ md: "kbd-md",
+ # "sm:kbd-lg"
+ # "@sm:kbd-lg"
+ # "md:kbd-lg"
+ # "@md:kbd-lg"
+ # "lg:kbd-lg"
+ # "@lg:kbd-lg"
+ lg: "kbd-lg",
+ # "sm:kbd-xl"
+ # "@sm:kbd-xl"
+ # "md:kbd-xl"
+ # "@md:kbd-xl"
+ # "lg:kbd-xl"
+ # "@lg:kbd-xl"
+ xl: "kbd-xl"
+ )
+ end
+end
diff --git a/spec/lib/phlexy_ui/kbd_spec.rb b/spec/lib/phlexy_ui/kbd_spec.rb
new file mode 100644
index 0000000..2836833
--- /dev/null
+++ b/spec/lib/phlexy_ui/kbd_spec.rb
@@ -0,0 +1,91 @@
+require "spec_helper"
+
+describe PhlexyUI::Kbd do
+ subject(:output) { render described_class.new }
+
+ it "is expected to match the formatted HTML" do
+ expected_html = html <<~HTML
+
+ HTML
+
+ is_expected.to eq(expected_html)
+ end
+
+ describe "conditions" do
+ {
+ xs: "kbd-xs",
+ sm: "kbd-sm",
+ md: "kbd-md",
+ lg: "kbd-lg",
+ xl: "kbd-xl"
+ }.each do |modifier, css|
+ context "when given :#{modifier} modifier" do
+ subject(:output) { render described_class.new(modifier) }
+
+ it "renders it apart from the main class" do
+ expected_html = html <<~HTML
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+ end
+
+ context "when given multiple conditions" do
+ subject(:output) { render described_class.new(:lg, :xs) }
+
+ it "renders them separately" do
+ expected_html = html <<~HTML
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+ end
+
+ describe "data" do
+ subject(:output) do
+ render described_class.new(data: {foo: "bar"})
+ end
+
+ it "renders it correctly" do
+ expected_html = html <<~HTML
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+
+ describe "responsiveness" do
+ %i[sm md lg xl @sm @md @lg @xl].each do |viewport|
+ context "when given an :#{viewport} responsive option" do
+ subject(:output) do
+ render described_class.new(:sm, responsive: {viewport => :lg})
+ end
+
+ it "renders it separately with a responsive prefix" do
+ expected_html = html <<~HTML
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+ end
+ end
+
+ describe "passing :as option" do
+ subject(:output) { render described_class.new(as: :span) }
+
+ it "renders as the given tag" do
+ expected_html = html <<~HTML
+
+ HTML
+
+ expect(output).to eq(expected_html)
+ end
+ end
+end