This is a template for creating applications using Vite 6 and HeroUI (v2).
If you appreciate my work, please consider giving it a star! 🤩
If you are looking for a template with OAuth2 authentication, you can check out my other repository: vite-react-heroui-auth0-template which is the same template with an OAuth2 authentication layer implemented via a free tier on Auth0.
This section explains how to create a new page with the default layout and add it to the navigation menus.
First, create a new file in the src/pages
directory. For example, let's create a "Contact" page:
// filepath: src/pages/contact.tsx
import { Trans, useTranslation } from "react-i18next";
import { title } from "@/components/primitives";
import DefaultLayout from "@/layouts/default";
export default function ContactPage() {
const { t } = useTranslation();
return (
<DefaultLayout>
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
<div className="inline-block max-w-lg text-center justify-center">
<h1 className={title()}>
<Trans t={t}>contact</Trans>
</h1>
<p className="mt-4 text-default-600">
This is the contact page content. You can add your contact form or information here.
</p>
</div>
</section>
</DefaultLayout>
);
}
Add the new page's translation key to each language file in the src/locales/base
directory:
Update the App.tsx
file to include a route for your new page:
// filepath: src/App.tsx
import ContactPage from "@/pages/contact";
function App() {
return (
<CookieConsentProvider>
<CookieConsent />
<Routes>
<Route element={<IndexPage />} path="/" />
<Route element={<DocsPage />} path="/docs" />
<Route element={<PricingPage />} path="/pricing" />
<Route element={<BlogPage />} path="/blog" />
<Route element={<AboutPage />} path="/about" />
{/* Add the new route */}
<Route element={<ContactPage />} path="/contact" />
</Routes>
</CookieConsentProvider>
);
}
Update the src/config/site.ts
file to include your new page in both the desktop navigation and mobile menu:
// filepath: src/config/site.ts
export const siteConfig = () => ({
// ... existing config
navItems: [
{
label: i18next.t("home"),
href: "/",
},
{
label: i18next.t("docs"),
href: "/docs",
},
{
label: i18next.t("pricing"),
href: "/pricing",
},
{
label: i18next.t("blog"),
href: "/blog",
},
{
label: i18next.t("about"),
href: "/about",
},
// Add the new page to desktop navigation
{
label: i18next.t("contact"),
href: "/contact",
},
],
navMenuItems: [
{
label: i18next.t("profile"),
href: "/profile",
},
// ... other mobile menu items
// Add the new page to mobile menu (before logout)
{
label: i18next.t("contact"),
href: "/contact",
},
{
label: i18next.t("logout"),
href: "/logout",
},
],
// ... rest of config
});
Start your development server and verify that:
- The new page is accessible via its route (e.g., http://localhost:5173/contact)
- The page appears in both desktop and mobile navigation menus
- The page title is correctly translated based on the selected language
That's it! You've successfully added a new page with the default layout and included it in the navigation menus.
This template uses i18next for internationalization. The configuration and available languages are defined in the src/i18n.ts
file.
This template supports multiple languages through i18next. Follow this comprehensive guide to add a new language:
Choose the appropriate language code using the ISO format:
- For region-specific language: use language-REGION format (e.g.,
fr-FR
,en-US
,pt-BR
) - For right-to-left languages (Arabic, Hebrew, etc.), make sure to set
isRTL: true
Open src/i18n.ts
and add your new language to the availableLanguages
array:
export const availableLanguages: AvailableLanguage[] = [
{ code: "en-US", nativeName: "English", isRTL: false, isDefault: true },
// Existing languages...
{ code: "pt-BR", nativeName: "Português do Brasil", isRTL: false }, // Add your new language
];
- Copy an existing translation file as a starting point:
# In your project root
cp src/locales/base/en-US.json src/locales/base/pt-BR.json
- Translate all values (right side) in the new file while keeping the keys (left side) unchanged:
{
"search": "Pesquisar",
"twitter": "Twitter",
"discord": "Discord",
// ... translate all other entries
}
In src/i18n.ts
, add a case for your new language in the loadPath
function:
backend: {
loadPath: (lng, ns) => {
let url: URL = new URL("./locales/base/en-US.json", import.meta.url);
switch (ns[0]) {
case "base":
switch (lng[0]) {
case "en-US":
url = new URL("./locales/base/en-US.json", import.meta.url);
break;
// ... existing languages
case "pt-BR": // Add your new language case
url = new URL("./locales/base/pt-BR.json", import.meta.url);
break;
default:
url = new URL("./locales/base/en-US.json", import.meta.url);
}
break;
default:
url = new URL("./locales/base/en-US.json", import.meta.url);
}
return url.toString();
},
}
For RTL Languages (Arabic, Hebrew, etc.):
- Set
isRTL: true
in the language definition - Ensure your UI components handle RTL layout properly
- Test thoroughly as some components may need specific RTL adjustments
For Languages with Special Characters:
- Ensure proper UTF-8 encoding in your JSON files
- Test with the longest translated strings to check for layout issues
For Chinese, Japanese, Korean:
- Consider using a shorter display format in the language switcher
- You might want to customize the language display in
LanguageSwitch
component
- Start your development server
- Switch to the newly added language using the language selector
- Verify all text is properly translated
- Check that special layouts (like RTL) work correctly
- Test on different screen sizes to ensure translations don't break layouts
To simplify the translation process, consider using:
- i18n Ally VS Code extension
- Export/import with spreadsheets for collaboration with translators
- Machine translation services for first drafts (DeepL, Google Translate)
- Use the included command to extract missing translations from the code
- Language not appearing in dropdown: Check that you've added it to the
availableLanguages
array correctly - Untranslated text: Ensure all keys from the base language exist in your new translation file
- Garbled text: Verify your JSON file is saved with UTF-8 encoding
- Layout issues: Some translations may be longer and need UI adjustments
The LanguageSwitch
component allows users to switch between the available languages. It is defined in the src/components/language-switch.tsx
file.
- The component uses the i18n instance to change the language and update the document metadata.
- It automatically updates the document direction based on the language (left-to-right or right-to-left).
- The selected language is stored in
localStorage
to persist the user's preference.
To use the LanguageSwitch
component in your application, simply include it in your JSX:
<LanguageSwitch availableLanguages={[{ code: "en-US", nativeName: "English", isRTL: false, isDefault: true },{ code: "fr-FR", nativeName: "Français", isRTL: false }]} />
or more simply using the availableLanguages
array defined in the src/i18n.ts
file:
import { availableLanguages } from "@/i18n";
<LanguageSwitch availableLanguages={availableLanguages} />
This component will render a dropdown menu with the available languages, allowing users to switch languages easily.
The default configuration uses the i18next-http-backend
plugin for language lazy loading. This means that translations are loaded only when needed, improving the application's performance.
- Configuration:
src/i18n.ts
- Translations:
src/locales/base
- Language Switch:
src/components/language-switch.tsx
By following the steps above, you can easily add new languages and manage internationalization for your application.
This template includes a cookie consent management system to comply with privacy regulations like GDPR. The system displays a modal dialog asking users for consent to use cookies and stores their preference in the browser's localStorage.
- Modern modal-based UI with blur backdrop
- Internationalized content for all supported languages
- Stores user preferences in localStorage
- Provides a context API for checking consent status throughout the application
- Supports both accepting and rejecting cookies
The cookie consent feature can be enabled or disabled through the site configuration:
- Enable/Disable Cookie Consent:
- Open the
src/config/site.ts
file - Set the
needCookieConsent
property totrue
orfalse
:
- Open the
export const siteConfig = () => ({
needCookieConsent: true, // Set to false if you don't need cookie consent
// ...other configuration
});
- Context Provider:
src/contexts/cookie-consent-context.tsx
- Provides a React context to manage consent state - UI Component:
src/components/cookie-consent.tsx
- Renders the consent modal using HeroUI components - Consent Status: The consent status can be one of three values:
pending
: Initial state, user hasn't made a decision yetaccepted
: User has accepted cookiesrejected
: User has rejected cookies
You can access the cookie consent status in any component using the useCookieConsent
hook:
import { useCookieConsent } from "@/contexts/cookie-consent-context";
const MyComponent = () => {
const { cookieConsent, acceptCookies, rejectCookies, resetCookieConsent } = useCookieConsent();
// Load analytics only if cookies are accepted
useEffect(() => {
if (cookieConsent === "accepted") {
// Initialize analytics, tracking scripts, etc.
}
}, [cookieConsent]);
// ...rest of your component
};
- Modify the appearance of the consent modal in
src/components/cookie-consent.tsx
- Add custom tracking or cookie management logic in the
acceptCookies
andrejectCookies
functions insrc/contexts/cookie-consent-context.tsx
- Update the cookie policy text in the language files (e.g.,
src/locales/base/en-US.json
)
This template uses Tailwind CSS 4, which is a utility-first CSS framework. You can customize the styles by modifying the tailwind.config.js
file.
Currently HeroUI uses Tailwind CSS 3, but @winchesHe create a port of HeroUI to Tailwind CSS 4, you can find it here, HeroUI packages are available at heroui-inc/heroui#4656 (comment).
To clone the project, run the following command:
git clone https://github.com/sctg-development/vite-react-heroui-template.git
In the vite.config.ts
file, all @heroui
packages are manually split into a separate chunk. This is done to reduce the size of the main bundle. You can remove this configuration if you don't want to split the packages.
You can use one of them npm
, yarn
, pnpm
, bun
, Example using npm
:
npm install
npm run dev
If you are using pnpm
, you need to add the following code to your .npmrc
file:
public-hoist-pattern[]=*@heroui/*
After modifying the .npmrc
file, you need to run pnpm install
again to ensure that the dependencies are installed correctly.
Licensed under the MIT license.