---
name: astro-new-component
description: "Create a new Astro component following the colocation pattern: ComponentName.astro + ComponentName.css + ComponentName.ts in the same folder."
---

# astro-new-component

Creates a new Astro component following the project's colocation pattern where all files related to a component (`.astro`, `.css`, `.ts`) live in the same folder.

## When to Apply

Use this skill when the user asks to:
- Create a new component, UI element, or page section
- Add a new button, card, form, modal, chart, or layout piece
- Build a new reusable block

## Colocation Pattern

```
src/components/ui/<category>/
└── ComponentName/
    ├── ComponentName.astro   ← always
    ├── ComponentName.css     ← if has styles (>10 lines)
    └── ComponentName.ts      ← if has JS interactivity
```

## Step-by-Step

### Step 1: Gather Requirements

Ask the user (or infer from context):
- **Component name**: PascalCase (e.g. `BlogPostCard`, `FocusTaskItem`)
- **Category/location**: `ui/blog`, `ui/dashboard`, `ui/buttons`, `ui/inputs`, `ui/charts`, `ui/cta/newsletter`, `layout`, `pages/blog`, etc.
- **Has styles?**: Does it need a `.css` file?
- **Has interactivity?**: Does it need a `.ts` file (DOM events, localStorage, API calls)?
- **New types?**: Are there new TypeScript interfaces to add to `src/lib/types/components.ts`?

### Step 2: Create the Folder

```
src/components/<category>/ComponentName/
```

### Step 3: Create `ComponentName.astro`

**Without CSS or TS** (simple component):
```astro
---
export interface Props {
  // define props here
}

const { prop1, prop2 = "default" } = Astro.props;
---

<div class="component-name">
  <slot />
</div>
```

**With CSS** (add import at end of frontmatter):
```astro
---
import "./ComponentName.css";

export interface Props {
  prop1: string;
  prop2?: boolean;
}

const { prop1, prop2 = false } = Astro.props;
---

<div class="component-name">
  <slot />
</div>
```

**With TS interactivity** (add script tag):
```astro
---
import "./ComponentName.css";

export interface Props {
  prop1: string;
}

const { prop1 } = Astro.props;
---

<div class="component-name" data-prop={prop1}>
  <slot />
</div>

<script>
  import { initComponentName } from "./ComponentName.ts";
  initComponentName();
</script>
```

**Using shared types from `src/lib/types/components.ts`**:
```astro
---
import "./ComponentName.css";
import type { ComponentNameProps } from "@/lib/types/components";

export interface Props extends ComponentNameProps {
  // additional props if needed
}

const { prop1, prop2 } = Astro.props;
---
```

### Step 4: Create `ComponentName.css` (if needed)

```css
/* ComponentName styles */
.component-name {
  /* base styles */
}

/* Use CSS custom properties for repeated values */
.component-name--variant {
  /* variant styles */
}
```

**Rules for CSS**:
- Use BEM-like class names: `.component-name`, `.component-name__element`, `.component-name--modifier`
- Use CSS custom properties for colors, spacing, and sizes instead of hardcoded values
- Include dark mode with `[data-theme="dark"] .component-name { }` if needed
- Add responsive breakpoints at the bottom

### Step 5: Create `ComponentName.ts` (if needed)

```typescript
export function initComponentName(): void {
  const elements = document.querySelectorAll<HTMLElement>(".component-name");
  
  elements.forEach((el) => {
    // attach event listeners
    el.addEventListener("click", handleClick);
  });
}

function handleClick(event: Event): void {
  // handle interaction
}
```

**Rules for `.ts`**:
- Export a single `init` function that the Astro `<script>` block calls
- Keep DOM queries inside the `init` function (not at module level) for ViewTransitions compatibility
- Use TypeScript generics for `querySelector<T>`
- If the component uses custom events, declare them in `src/lib/types/events.ts`

### Step 6: Add Types to `src/lib/types/components.ts` (if needed)

Open `src/lib/types/components.ts` and add:

```typescript
// ComponentName
export interface ComponentNameProps {
  prop1: string;
  prop2?: boolean;
}
```

## Rules

- **Never** put more than 10 lines of CSS inside `<style>` in the `.astro` file — extract to `.css`
- **Never** put logic (loops, conditionals, DOM queries) inside `<script>` inline — extract to `.ts`
- **Always** use `import "./ComponentName.css"` in the frontmatter (not `<style>@import</style>`)
- **Always** use `<script>import "./ComponentName.ts"</script>` for the TypeScript entry point
- **Always** define `Props` interface inside the `.astro` file, and export shared types from `src/lib/types/components.ts`

## Example

User asks: "Create a TagBadge component for the blog"

```
src/components/ui/blog/TagBadge/
├── TagBadge.astro
└── TagBadge.css
```

**TagBadge.astro**:
```astro
---
import "./TagBadge.css";

export interface Props {
  label: string;
  href?: string;
  active?: boolean;
}

const { label, href, active = false } = Astro.props;
const Tag = href ? "a" : "span";
---

<Tag
  class:list={["tag-badge", { "tag-badge--active": active }]}
  href={href}
>
  {label}
</Tag>
```

**TagBadge.css**:
```css
.tag-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.25rem 0.75rem;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 500;
  background: var(--tag-bg, #f4f4f5);
  color: var(--tag-color, #52525b);
  text-decoration: none;
  transition: background 0.15s;
}

.tag-badge--active,
.tag-badge:hover {
  background: var(--tag-bg-active, #2476ff);
  color: #fff;
}
```
