diff --git a/packages/frontend/app/components/bottom-navigation.stories.tsx b/packages/frontend/app/components/bottom-navigation.stories.tsx
new file mode 100644
index 0000000..78a8913
--- /dev/null
+++ b/packages/frontend/app/components/bottom-navigation.stories.tsx
@@ -0,0 +1,121 @@
+import { Gift, QrCode, Scan, Send } from "lucide-react";
+import type * as React from "react";
+import {
+ MemoryRouter,
+ Route,
+ Routes,
+ type To,
+ useLocation,
+} from "react-router";
+
+import "~/app.css";
+import {
+ BottomNavigation,
+ BottomNavigationItem,
+} from "~/components/ui/bottom-navigation";
+
+export default {
+ title: "Components/BottomNavigation",
+};
+
+type StoryFrameProps = {
+ children: React.ReactNode;
+ initialEntry?: string;
+};
+
+function StoryFrame({ children, initialEntry = "/send" }: StoryFrameProps) {
+ return (
+
+
+ {children}} />
+
+
+ );
+}
+
+function StoryContent({ children }: { children: React.ReactNode }) {
+ const location = useLocation();
+ return (
+
+
+
+ 現在のパス: {location.pathname}
+
+
+ {children}
+
+ );
+}
+
+const ICON_SIZE = 20;
+
+type NavItem = {
+ icon: React.ReactNode;
+ label: string;
+ to: To;
+};
+
+const navItems: NavItem[] = [
+ { icon: , label: "送る", to: "/send" },
+ { icon: , label: "スキャン", to: "/scan" },
+ { icon: , label: "マイコード", to: "/mycode" },
+ { icon: , label: "出品", to: "/listing" },
+];
+
+export const Default = () => {
+ return (
+
+
+ {navItems.map((item) => (
+
+ ))}
+
+
+ );
+};
+
+Default.storyName = "Default (1st Active)";
+
+export const ThirdActive = () => {
+ return (
+
+
+ {navItems.map((item) => (
+
+ ))}
+
+
+ );
+};
+
+ThirdActive.storyName = "3rd Item Active";
+
+export const WithDisabled = () => {
+ return (
+
+
+ {navItems.map((item, i) => (
+
+ ))}
+
+
+ );
+};
+
+WithDisabled.storyName = "With Disabled Item";
diff --git a/packages/frontend/app/components/ui/bottom-navigation.tsx b/packages/frontend/app/components/ui/bottom-navigation.tsx
new file mode 100644
index 0000000..bab7ddc
--- /dev/null
+++ b/packages/frontend/app/components/ui/bottom-navigation.tsx
@@ -0,0 +1,84 @@
+import * as React from "react";
+import { NavLink, type To } from "react-router";
+
+import { cn } from "~/lib/utils";
+
+export interface BottomNavigationProps
+ extends React.HTMLAttributes {
+ children: React.ReactNode;
+}
+
+export const BottomNavigation = React.forwardRef<
+ HTMLElement,
+ BottomNavigationProps
+>(({ className, children, ...props }, ref) => {
+ return (
+
+ );
+});
+BottomNavigation.displayName = "BottomNavigation";
+
+export interface BottomNavigationItemProps {
+ "aria-label"?: string;
+ className?: string;
+ disabled?: boolean;
+ icon: React.ReactNode;
+ label: string;
+ to: To;
+}
+
+export const BottomNavigationItem = React.forwardRef<
+ HTMLAnchorElement,
+ BottomNavigationItemProps
+>(
+ (
+ { "aria-label": ariaLabel, className, disabled = false, icon, label, to },
+ ref,
+ ) => {
+ if (disabled) {
+ return (
+
+ {icon}
+ {label}
+
+ );
+ }
+
+ return (
+
+ cn(
+ "flex w-[72px] flex-col items-center gap-2 rounded-full px-8 py-6 text-primary-foreground transition-colors outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
+ isActive && "bg-alpha-black-25",
+ className,
+ )
+ }
+ >
+ {icon}
+ {label}
+
+ );
+ },
+);
+BottomNavigationItem.displayName = "BottomNavigationItem";