diff --git a/packages/frontend/src/components/coachmark/Coachmark.tsx b/packages/frontend/src/components/coachmark/Coachmark.tsx
new file mode 100644
index 0000000..b54024f
--- /dev/null
+++ b/packages/frontend/src/components/coachmark/Coachmark.tsx
@@ -0,0 +1,60 @@
+import ReactJoyride, { type Props as JoyrideProps } from "react-joyride";
+
+import color from "../../design-system/tokens/color";
+import { coachmarkZIndex } from "../../design-system/tokens/utils.css";
+import useMount from "../../hooks/useMount";
+
+import { CoachTooltip } from "./CoachTooltip";
+
+export type CoachmarkProps = Partial<
+ Omit<
+ JoyrideProps,
+ | "locale"
+ | "styles"
+ | "tooltipComponent"
+ | "spotlightPadding"
+ | "floaterProps"
+ >
+>;
+
+export function Coachmark(props: CoachmarkProps) {
+ const { mounted } = useMount();
+
+ if (!mounted) return null;
+ return (
+
+ );
+}
+
+type RequiredJoyrideProps = Required;
+
+const LOCALE: RequiredJoyrideProps["locale"] = {
+ back: "이전",
+ next: "다음",
+ close: "닫기",
+ last: "종료",
+ skip: "건너뛰기",
+};
+
+const STYLES: RequiredJoyrideProps["styles"] = {
+ options: {
+ overlayColor: "rgba(0, 0, 0, 0.2)",
+ primaryColor: color.$semantic.primary,
+ zIndex: coachmarkZIndex,
+ },
+};
+
+const FLOATER_PROPS: RequiredJoyrideProps["floaterProps"] = {
+ offset: 25,
+ styles: {
+ arrow: { length: 15, spread: 18 },
+ floater: { filter: "none" },
+ },
+};
diff --git a/packages/frontend/src/components/coachmark/index.ts b/packages/frontend/src/components/coachmark/index.ts
new file mode 100644
index 0000000..ec1ddc4
--- /dev/null
+++ b/packages/frontend/src/components/coachmark/index.ts
@@ -0,0 +1 @@
+export { type CoachmarkProps, Coachmark } from "./Coachmark";