diff --git a/apps/community/src/app/my/change-password/page.tsx b/apps/community/src/app/my/change-password/page.tsx
new file mode 100644
index 0000000..ecf92f6
--- /dev/null
+++ b/apps/community/src/app/my/change-password/page.tsx
@@ -0,0 +1,16 @@
+import ChangePasswordForm from '~/components/my/change-password/change-password-form';
+import { PageHeader } from '~/components/page-header';
+
+const ChangePasswordPage = () => {
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export default ChangePasswordPage;
diff --git a/apps/community/src/components/my/change-password/change-password-form.css.ts b/apps/community/src/components/my/change-password/change-password-form.css.ts
new file mode 100644
index 0000000..bb35c80
--- /dev/null
+++ b/apps/community/src/components/my/change-password/change-password-form.css.ts
@@ -0,0 +1,27 @@
+import { screen, themeVars } from '@aics-client/design-system/styles';
+import { style } from '@vanilla-extract/css';
+
+const formWrapper = style({
+ display: 'flex',
+ flexDirection: 'column',
+ margin: '0 auto',
+ gap: themeVars.spacing.xl,
+ width: '60%',
+});
+
+const newPasswordWrapper = style({
+ display: 'grid',
+ gap: themeVars.spacing.xl,
+ ...screen.lg({
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ gap: themeVars.spacing.md,
+ }),
+});
+
+const inputField = style({
+ width: '100%',
+});
+
+export { formWrapper, newPasswordWrapper, inputField };
diff --git a/apps/community/src/components/my/change-password/change-password-form.tsx b/apps/community/src/components/my/change-password/change-password-form.tsx
new file mode 100644
index 0000000..ab76d65
--- /dev/null
+++ b/apps/community/src/components/my/change-password/change-password-form.tsx
@@ -0,0 +1,89 @@
+'use client';
+
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
+
+import { Button, Input } from '@aics-client/design-system';
+import * as styles from '~/components/my/change-password/change-password-form.css';
+
+const changePasswordSchema = z
+ .object({
+ currentPassword: z
+ .string()
+ .min(1, { message: '현재 비밀번호를 입력해주세요.' }),
+ newPassword: z
+ .string()
+ .min(8, { message: '비밀번호는 최소 8자 이상이어야 합니다.' })
+ .regex(
+ /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[~!@#$%^&*])[a-zA-Z0-9~!@#$%^&*]{8,15}$/,
+ { message: '비밀번호는 영문, 숫자, 특수문자를 포함해야 합니다.' },
+ ),
+ confirmNewPassword: z
+ .string()
+ .min(1, { message: '새 비밀번호 확인을 입력해주세요.' }),
+ })
+ .refine((data) => data.newPassword === data.confirmNewPassword, {
+ message: '비밀번호가 일치하지 않습니다.',
+ path: ['confirmNewPassword'],
+ });
+
+const defaultValues = {
+ currentPassword: '',
+ newPassword: '',
+ confirmNewPassword: '',
+};
+
+const ChangePasswordForm = () => {
+ const {
+ register,
+ handleSubmit,
+ formState: { errors, isValid },
+ } = useForm>({
+ resolver: zodResolver(changePasswordSchema),
+ mode: 'onChange',
+ defaultValues,
+ });
+
+ const onSubmit = (data: z.infer) => {
+ // TODO: 비밀번호 변경 API 호출
+ console.log(data);
+ };
+
+ return (
+
+ );
+};
+
+export default ChangePasswordForm;
diff --git a/packages/design-system/src/components/button/button.css.ts b/packages/design-system/src/components/button/button.css.ts
index 15e3d5d..9869020 100644
--- a/packages/design-system/src/components/button/button.css.ts
+++ b/packages/design-system/src/components/button/button.css.ts
@@ -47,6 +47,7 @@ const buttonVariants = recipe({
disabled: {
true: {
cursor: 'default',
+ opacity: themeVars.opacity[75],
},
false: {
cursor: 'pointer',