Extract button component
You extracted the Card component manually to learn the pattern. Now you'll apply that same pattern to extract the Button from the home page. This time, you'll add a variant prop to support multiple button styles (primary and secondary), making the component more flexible and reusable across different contexts.
Outcome
Extract the Button component to the shared package with support for multiple style variants.
Fast track
- Create
Buttoncomponent inpackages/uiwith variant support - Export
Buttonfrom the package - Update home page to use the shared
Button - Test both primary and secondary variants
Hands-on exercise 2.4
Extract Button component with variant support using the same pattern you learned with Card.
Requirements:
- Create
Buttoncomponent inpackages/ui/src/button.tsxwith variant prop - Export
Buttonfrom package (package.json + index.ts) - Update home page to use
<Button>from shared package - Test both primary and secondary variants
Implementation hints:
Buttonsupports primary (blue) and secondary (gray) variants- The variant prop defaults to 'primary'
- Use the same export pattern as
Card(package.json exports field + index.ts)
Files to create/modify:
packages/ui/src/button.tsx(new component)packages/ui/package.json(add Button export)packages/ui/src/index.ts(export Button)apps/web/app/page.tsx(use Button from shared package)
Extract button component
The home page has an inline button. Let's extract it to the UI package.
Create packages/ui/src/button.tsx with the Button code:
export interface ButtonProps {
children: React.ReactNode
onClick?: () => void
variant?: 'primary' | 'secondary'
}
export function Button({ children, onClick, variant = 'primary' }: ButtonProps) {
const baseStyles = {
padding: '0.75rem 1.5rem',
fontSize: '1rem',
border: 'none',
borderRadius: '0.5rem',
cursor: 'pointer',
fontWeight: '600',
}
const variantStyles = {
primary: {
backgroundColor: '#0070f3',
color: 'white',
},
secondary: {
backgroundColor: '#f5f5f5',
color: '#333',
border: '1px solid #e5e7eb',
},
}
return (
<button
onClick={onClick}
style={{ ...baseStyles, ...variantStyles[variant] }}
>
{children}
</button>
)
}The variant prop lets you choose between button styles: primary (blue, for main actions) or secondary (gray, for less prominent actions). This makes the Button reusable across different contexts without hardcoding the styles.
Add button to package exports
Update packages/ui/package.json:
{
"exports": {
"./button": "./src/button.tsx",
"./card": "./src/card.tsx"
}
}Export from the index file:
export { Button } from './button'
export { Card } from './card'Use button in home page
Update the home page to use the shared Button:
import Link from 'next/link'
import { Button } from '@geniusgarage/ui/button'
export default function Home() {
return (
<main style={{ padding: '2rem', fontFamily: 'system-ui', maxWidth: '800px', margin: '0 auto' }}>
<nav style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '2rem' }}>
<Link href="/features" style={{ textDecoration: 'none', color: '#0070f3', fontWeight: 'bold' }}>
Features
</Link>
</nav>
<div style={{ textAlign: 'center', marginTop: '4rem' }}>
<h1 style={{ fontSize: '3rem', marginBottom: '1rem' }}>🧠GeniusGarage</h1>
<p style={{ fontSize: '1.5rem', color: '#666', marginBottom: '2rem' }}>
Manage and share your code snippets
</p>
<Button>Get Started</Button>
<p style={{ color: '#666', marginTop: '3rem', fontSize: '0.875rem' }}>
This is the starter project. You'll build out the full platform as you progress through the course.
</p>
</div>
</main>
)
}Much cleaner! The inline button styles are gone.
Run and see it work
Start the dev server:
pnpm devOpen http://localhost:3000 - the button looks identical but now uses the shared component.
Test the variant prop
Let's verify the variant prop works. Change the Button to use the secondary variant:
<Button variant="secondary">Get Started</Button>Refresh the browser. The button should now be gray with a border instead of blue.
Change it back to primary (or remove the variant prop since it defaults to primary):
<Button>Get Started</Button>The button returns to blue. The variant system works!
What you built
You now have two shared components in your UI package:
packages/ui:
Cardcomponent (manually extracted in previous lesson)Buttoncomponent with variant support (primary and secondary)- Proper TypeScript types exported for both
The pattern you've learned:
- Create component in
packages/ui/src/ - Add to
package.jsonexports field - Export from
index.ts - Import in any app with
@geniusgarage/ui/component-name
This pattern scales - you could have 50 components following this exact structure.
Commit
Save your work:
git add .
git commit -m "feat(ui): extract Button component with variants"Done-when
Verify your implementation:
- Button component exists at
packages/ui/src/button.tsxwith variant prop - Button exported from
packages/ui/package.jsonexports field - Button exported from
packages/ui/src/index.ts - Home page uses
<Button>from@geniusgarage/ui/button - Button displays correctly on home page
What's Next
Next lesson: Deploy Web App - you'll push to GitHub, deploy to Vercel, and see Turborepo's remote caching in production. Change a README → cache hit in CI. Change the UI package → selective rebuild. This is where monorepos with Turborepo really shine at scale.
Was this helpful?