Build an Invoice Generator with Git

Build a browser-based invoice generator and track your work with Git.

Introduction

30 Second Summary

Every business sends invoices. Whether you are a freelancer billing a client or a shop tracking payments, invoices are a universal part of working life.

In this project, you will build a browser-based invoice generator from scratch using HTML, CSS, and JavaScript. You will track every change you make with Git and push it to GitHub so your work is always backed up and shareable.

What You'll Build

Picture filling in a client name, adding a few line items, and watching the subtotal, tax, and grand total update instantly as you type. Then you click one button and the whole thing transforms into a clean, printable invoice you could hand to a real client.

By the end of this project, you'll have:

  • A dynamic invoice form where you add line items, change quantities and prices, and watch the totals recalculate in real time.
  • A one-click Generate Invoice feature that transforms your editable form into a polished, read-only document ready for printing.
  • A GitHub repository with a clean commit history showing exactly how you built the project from start to finish.
  • Secret Mission: Add localStorage to save and load invoice drafts so you never lose your work.

Are there any prerequisites?

You should be familiar with basic HTML and CSS concepts like elements, tags, and attributes. No JavaScript or Git experience is required.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Set Up Git and Your Project

Every professional project starts with version control from day one. This means nothing is ever lost, and every change you make is traceable.

In this step, you will verify Git is working on your machine, create a GitHub repository to store your code online, and set up a project folder with three starter files.

In this step, get ready to:
  • Verify Git is installed and configure your identity.
  • Create a GitHub repository and clone it to your computer.
  • Add starter files, commit, and push to GitHub.
Verify Git is installed and configure your identity

Before writing any code, you need to confirm Git is working and tell it who you are. Git bakes your name and email into every commit so collaborators know who made each change.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code and press Enter to open it.
  • Click Terminal in the top menu bar.
  • Click New Terminal.

A terminal panel opens at the bottom of VS Code. This is where you will run all your Git commands.

  • Confirm Git is installed by running this command:
git --version

You should see a version number printed, such as git version 2.43.0. The exact number does not matter as long as a version appears.

Don't see a version number?

If you see a "command not found" error, Git may not be installed or not added to your system PATH. Revisit the Git installation page at git-scm.com/downloads and follow the instructions for your operating system.

Help me fix my Git PATH issue

  • Configure your Git identity by running these commands:
git config --global user.name "[[YOUR_NAME="Your Name"]]"
git config --global user.email "[[YOUR_EMAIL="your@email.com"]]"
git config --global init.defaultBranch main

What do these commands do?

  • user.name and user.email attach your identity to every commit you make. Use the same email as your GitHub account.
  • init.defaultBranch main ensures new repositories start with a branch called main instead of the older default name.
  • Verify your name was saved by running:
git config --global user.name

You should see the name you just entered printed back to you.

Create a new repository on GitHub

A repository is where GitHub stores your project files and their entire change history. You will create an empty one on GitHub first, then clone it down to your machine.

  • Open Google Chrome and go to github.com.
  • Click the + icon in the top right corner.
  • Click New repository.
  • In the Repository name field, type invoice-generator.
  • Leave the repository set to Public.
  • Do NOT check Add a README file or any other initialization options.
  • Click Create repository.

GitHub shows you a page with setup instructions. You now have an empty repository ready to receive your code.

Why leave it empty?

Adding a README or license during creation puts a commit on the remote that your local machine does not have. This causes merge conflicts the first time you push. Starting empty avoids that entirely.

  • Switch back to VS Code's terminal. Move to your Desktop by running this command:
cd ~/Desktop
  • Clone your new repository by running this command:
git clone https://github.com/[[GITHUB_USERNAME="YOUR-USERNAME"]]/invoice-generator.git

You should see output that mentions Cloning into 'invoice-generator'... followed by a confirmation. Git has created an invoice-generator folder on your Desktop.

Seeing an authentication prompt?

GitHub may open a browser window asking you to sign in and authorize access. Follow the prompts to allow it. This is standard behavior for HTTPS connections to GitHub.

Help me fix my authentication error

  • Move into your new project folder by running:
cd invoice-generator
  • Open this folder in VS Code. Click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the invoice-generator folder, and click Open.

VS Code reloads with invoice-generator as the active workspace. You should see the folder name in the Explorer sidebar on the left.

Create starter files and push to GitHub

Every web project needs at least three files: one for structure, one for styling, and one for behavior. You will create all three now and push them to GitHub as your first real commit.

  • Open a new terminal in this workspace. Click Terminal in the top menu bar.
  • Click New Terminal.
  • Create your three project files by running this command:
touch index.html style.css script.js
  • Confirm the files exist by running:
ls

You should see index.html, style.css, and script.js listed in the output. They also appear in the Explorer sidebar on the left.

Using Windows PowerShell instead of bash?

If touch is not recognized, your terminal is using PowerShell. Run this equivalent command instead:

New-Item index.html, style.css, script.js

Alternatively, click the terminal dropdown (next to the + icon in the terminal panel) and select Git Bash to use bash commands on Windows.

Help me create files on Windows

  • In the Explorer sidebar, click on index.html to open it in the editor.
  • Paste the following HTML boilerplate into index.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Invoice Generator</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <script src="script.js"></script>
</body>
</html>

What does this code do?

  • The <!DOCTYPE html> declaration tells the browser this is a modern HTML5 document.
  • The <link> tag connects your CSS file so styles load with the page.
  • The <script> tag connects your JavaScript file so it runs after the page structure loads.
  • Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).

✔️ Awesome, I've got everything!

Great. Make sure you have saved index.html. The other two files (style.css and script.js) stay empty for now.

ⓧ I'd like to double check the full code

Your index.html should look exactly like this:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Invoice Generator</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <script src="script.js"></script>
</body>
</html>

The files style.css and script.js should exist but remain completely empty.

Now you will stage your files, create a commit, and push everything to GitHub.

  • Stage, commit, and push your starter files by running these commands:
git add .
git commit -m "feat: add project starter files"
git push

What do these commands do?

  • git add . stages all new and changed files, telling Git "include these in my next commit."
  • git commit -m creates a snapshot of your staged files with a message describing what changed.
  • git push uploads your local commit to GitHub so it is stored safely online.

Before you check GitHub, what do you expect to see on your repository page? Think about which files should appear and what message should show next to them.

  • Switch to Chrome and go to your repository page at github.com/YOUR-USERNAME/invoice-generator.
  • Refresh the page.

You should see your three files listed: index.html, script.js, and style.css. Next to them, the commit message feat: add project starter files is displayed.

Don't see your files on GitHub?

  • Make sure the git push command completed without errors. Look for a line mentioning main -> main in the terminal output.
  • If you see a login prompt or authentication error, follow the browser sign-in flow that GitHub opens. Then run git push again.
  • Confirm you are on the correct repository URL. It should match the name invoice-generator under your GitHub username.

Help me troubleshoot my push to GitHub

Your project is set up with version control from the very first file. Next up, you will build the full invoice form structure with HTML so there is something real to see in the browser.

Build the Invoice HTML Structure

In Step 1, you created your project files and pushed your first commit to GitHub. Now it's time to give your invoice generator its shape.

Every web page starts with its HTML skeleton. You need form elements to capture invoice data and a table to organize line items before any styling or interactivity can happen.

In this step, get ready to:
  • Build the form with company, invoice, and client input fields.
  • Create a line items table with a starter row and action buttons.
  • Add the output structure for the generated invoice view.
Build the invoice header and client section

An invoice has two logical groups at the top. A "From" section holds your company details. An "Invoice" section holds the invoice number and date. Below both, a "Bill To" section captures who you are billing.

  • In VS Code, open index.html from the Explorer sidebar.
  • Find the <script src="script.js"></script> line inside the <body> tag.
  • Add the following code directly above that <script> line:
    <div class="container">
        <div id="invoice-form">
            <h1>Invoice Generator</h1>

            <div class="invoice-header">
                <div class="company-details">
                    <h2>From</h2>
                    <input type="text" id="company-name" placeholder="Your Company Name">
                    <input type="text" id="company-address" placeholder="Address">
                    <input type="text" id="company-email" placeholder="Email">
                </div>
                <div class="invoice-details">
                    <h2>Invoice</h2>
                    <label>Invoice #</label>
                    <input type="text" id="invoice-number" placeholder="INV-001">
                    <label>Date</label>
                    <input type="date" id="invoice-date">
                </div>
            </div>

            <div class="client-details">
                <h2>Bill To</h2>
                <input type="text" id="client-name" placeholder="Client Name">
                <input type="text" id="client-address" placeholder="Client Address">
                <input type="text" id="client-email" placeholder="Client Email">
            </div>

What does this code do?

  • The container div wraps the entire invoice in one centered box.
  • The invoice-form div holds everything the user can edit. Later, JavaScript will toggle between this view and a read-only output view.
  • The invoice-header div groups the "From" and "Invoice" sections side by side.
  • Each input has a unique id so JavaScript can read its value later.
  • Save index.html.
  • Open the file in Chrome to check your progress. Right-click index.html in the VS Code Explorer and click Open with Live Server. If you do not have the Live Server extension, drag the file from your file manager into Chrome instead.
  • You should see a heading, three text inputs labelled "From", two inputs labelled "Invoice", and three inputs labelled "Bill To". It looks plain because there is no CSS yet.

Don't see any form fields?

  • Make sure you pasted the code above the <script> tag, not below it. Everything visible must be inside <body> and before the script.
  • Check for missing opening or closing angle brackets in your HTML tags.
  • Ask for help: Why is my index.html showing a blank page in Chrome?
Add the line items table and totals

The core of any invoice is its line items. You need a table where each row has a description, quantity, price, and a calculated total. You also need buttons to add or remove rows, and a summary showing subtotal, tax, and grand total.

  • In index.html, place your cursor directly below the closing </div> of the client-details section you just added.
  • Add the line items table by pasting the following code:
            <table id="line-items">
                <thead>
                    <tr>
                        <th>Description</th>
                        <th>Quantity</th>
                        <th>Unit Price</th>
                        <th>Total</th>
                        <th></th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td><input type="text" class="item-description" placeholder="Item description"></td>
                        <td><input type="number" class="item-quantity" min="0" value="0"></td>
                        <td><input type="number" class="item-price" min="0" step="0.01" value="0"></td>
                        <td class="item-total">0.00</td>
                        <td><button class="remove-row" type="button">Remove</button></td>
                    </tr>
                </tbody>
            </table>

What does this table do?

  • The thead defines column headers. The empty fifth <th> reserves space for the Remove button.
  • The tbody has one starter row with inputs for description, quantity, and price. JavaScript will add more rows dynamically later.
  • Input classes like item-quantity let JavaScript find and read values from every row at once.
  • Directly below the table's closing </table> tag, add the buttons and totals section:
            <button id="add-item" type="button">Add Item</button>

            <div class="totals">
                <div class="totals-row">
                    <span>Subtotal:</span>
                    <span id="subtotal">0.00</span>
                </div>
                <div class="totals-row">
                    <span>Tax (10%):</span>
                    <span id="tax">0.00</span>
                </div>
                <div class="totals-row total-final">
                    <span>Total:</span>
                    <span id="total">0.00</span>
                </div>
            </div>

            <button id="generate-btn" type="button">Generate Invoice</button>
        </div>

What does this section do?

  • The Add Item button (#add-item) will trigger JavaScript to insert a new table row.
  • Each totals span has an id so JavaScript can update the displayed numbers whenever values change.
  • The Generate Invoice button (#generate-btn) will later switch to a read-only output view.
  • The closing </div> at the bottom closes the invoice-form wrapper.
  • Save index.html and refresh Chrome.
  • You should now see the full form: company inputs, client inputs, a table row with Description, Quantity, Unit Price, and Total columns, a Remove button, an Add Item button, the three totals lines, and a green-less Generate Invoice button at the bottom.

Table not showing up?

  • Make sure the table code sits inside the invoice-form div (after client-details, before the closing </div> of invoice-form).
  • Check that your <table> tag has a matching </table> tag. A missing closing tag can collapse the layout.
  • Ask for help: My line items table is not rendering in the browser.

Next, you need a hidden output section. When the learner clicks Generate Invoice, the form will hide and a clean read-only view will appear instead. You will wire this up with JavaScript in a later step, but the HTML structure needs to exist now.

  • Below the closing </div> of invoice-form (but still inside the container div), add the first half of the output section:
        <div id="invoice-output" class="hidden">
            <div class="output-header">
                <div class="output-company">
                    <h1 id="output-company-name"></h1>
                    <p id="output-company-address"></p>
                    <p id="output-company-email"></p>
                </div>
                <div class="output-invoice-info">
                    <h2>INVOICE</h2>
                    <p>Invoice #: <span id="output-invoice-number"></span></p>
                    <p>Date: <span id="output-invoice-date"></span></p>
                </div>
            </div>

            <div class="output-client">
                <h3>Bill To:</h3>
                <p id="output-client-name"></p>
                <p id="output-client-address"></p>
                <p id="output-client-email"></p>
            </div>
  • Directly below that, add the output table, totals, action buttons, and closing tags:
            <table class="output-table">
                <thead>
                    <tr>
                        <th>Description</th>
                        <th>Quantity</th>
                        <th>Unit Price</th>
                        <th>Total</th>
                    </tr>
                </thead>
                <tbody id="output-items"></tbody>
            </table>

            <div class="output-totals">
                <div class="totals-row">
                    <span>Subtotal:</span>
                    <span id="output-subtotal"></span>
                </div>
                <div class="totals-row">
                    <span>Tax (10%):</span>
                    <span id="output-tax"></span>
                </div>
                <div class="totals-row total-final">
                    <span>Total:</span>
                    <span id="output-total"></span>
                </div>
            </div>

            <div class="output-actions no-print">
                <button id="edit-btn" type="button">Edit</button>
                <button id="print-btn" type="button">Print</button>
            </div>
        </div>
    </div>

Why is this section hidden?

The class="hidden" on invoice-output means CSS will hide it with display: none. It only becomes visible when JavaScript removes that class after the user clicks Generate Invoice.

This output section mirrors the form's structure but uses <p> and <span> elements instead of inputs. That produces a clean, read-only document suitable for printing.

  • Save index.html and refresh Chrome.
  • The page should look the same as before because the output section is hidden. To confirm it exists, press F12 to open DevTools, click the Elements tab, and look for a div with id="invoice-output" in the DOM tree.
Verify the structure and push to GitHub

Before you think about styling, take a moment to predict what this page would look like to someone opening it for the first time.

  • Look at your page in Chrome. Do you think it looks ready to use as an invoice? What is missing?

You should see an unstyled but complete form with all the input fields, a table row, and buttons. It looks rough, but the structure is all there. What is missing is CSS to make it look professional, and JavaScript to make it functional.

  • Stage, commit, and push your changes by running these commands in the VS Code terminal:
git add .
git commit -m "feat: add invoice HTML structure with form and table"
git push
  • Go to your repository on github.com and refresh the page.
  • You should see your three files listed with the commit message feat: add invoice HTML structure with form and table displayed.

Push rejected or authentication error?

  • If Git asks you to authenticate, a browser window should open for GitHub sign-in. Follow the prompts and try git push again.
  • If you see fatal: not a git repository, make sure your terminal is inside the invoice-generator folder. Run cd invoice-generator and try again.
  • Ask for help: I cannot push my HTML commit to GitHub.

✔️ Awesome, I've got everything!

Great. Double check you have saved index.html and that your commit is visible on GitHub.

ⓧ I'd like to double check the full code

Your index.html is built from five code blocks you added in order. Scroll up and confirm each section is present inside <body>, in this order:

  • The container div opening, invoice-form div, invoice-header (From + Invoice sections), and client-details (Bill To).
  • The line-items table with thead and one tbody row.
  • The add-item button, totals div with three rows, generate-btn button, and closing </div> for invoice-form.
  • The invoice-output div with class="hidden": output-header, output-client.
  • The output-table, output-totals, output-actions (Edit and Print buttons), and closing </div> tags for invoice-output and container.
  • The <script src="script.js"></script> line remains at the bottom, right before </body>.

Your HTML skeleton is complete. Next up, you will add CSS to transform this bare form into a professional-looking invoice.

Style the Invoice with CSS

In the last step, you built the full HTML skeleton for your invoice. It has all the right fields, a table, and buttons. But right now it looks like a wall of unstyled text that no client would take seriously.

CSS transforms your skeleton into something professional and credible. You will use Flexbox to lay out sections side by side, colour-coded buttons to signal different actions, and print-specific rules so the invoice looks clean on paper.

In this step, get ready to:
  • Set up base styles and a centered card layout with Flexbox.
  • Style the table, inputs, and buttons with distinct colours and hover effects.
  • Add print-ready rules and commit your styled invoice to GitHub.
Set up base styles and Flexbox layout

Every professional web page starts with a CSS reset to remove browser defaults, then builds up a consistent visual foundation. You will also centre the invoice on the page inside a clean white card.

  • Open style.css in VS Code (it is currently empty) and add the base reset, body styles, and container by pasting this code:
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f5f5f5;
    color: #333;
    line-height: 1.6;
}

.container {
    max-width: 800px;
    margin: 40px auto;
    padding: 40px;
    background-color: #fff;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    border-radius: 8px;
}

What does this code do?

  • The * selector resets all margins and paddings to zero so every browser starts from the same baseline.
  • body sets a light grey background and consistent font so the invoice floats on a neutral canvas.
  • .container creates the white card effect with margin: 40px auto centering it horizontally and box-shadow adding a subtle depth.
  • Save style.css and refresh your browser.
  • You should see the invoice content sitting inside a white card centred on a grey background.

Still seeing unstyled text?

  • Confirm the <link rel="stylesheet" href="style.css"> tag exists in your index.html <head> section.
  • Make sure you saved style.css (look for the dot on the file tab in VS Code indicating unsaved changes).
  • Hard-refresh the page with Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows) to bypass the cache.

Why isn't my CSS loading in the browser?

  • Next, add heading styles and the Flexbox layout that places the From and Invoice sections side by side. Paste this below the .container block:
h1 {
    font-size: 1.8rem;
    margin-bottom: 20px;
    color: #2c3e50;
}

h2 {
    font-size: 1.2rem;
    margin-bottom: 10px;
    color: #2c3e50;
}

h3 {
    font-size: 1rem;
    margin-bottom: 8px;
    color: #555;
}

.invoice-header {
    display: flex;
    justify-content: space-between;
    gap: 40px;
    margin-bottom: 30px;
}

.company-details,
.invoice-details {
    flex: 1;
}

.client-details {
    margin-bottom: 30px;
}

What does this code do?

  • The heading selectors set a consistent dark blue colour (#2c3e50) across all heading levels.
  • .invoice-header uses display: flex to arrange the From and Invoice sections horizontally with equal width (flex: 1).
  • justify-content: space-between pushes the two sections to opposite edges, and gap: 40px adds breathing room between them.
  • Save style.css and refresh your browser.
  • The From section and Invoice section now sit side by side instead of stacking vertically. Headings appear in dark blue.

Sections still stacking vertically?

  • Check that you typed .invoice-header (with the dot) and not invoice-header without it. CSS class selectors require the leading dot.
  • Confirm the class name in your CSS matches class="invoice-header" exactly in index.html.

Why isn't Flexbox making my sections side by side?

Style inputs, table, and buttons

The layout is set. Now you will give inputs a clean look, make the table header stand out with a dark background, and colour-code each button so the learner instantly knows what each one does.

  • Add the input and label styles below the .client-details block in style.css:
input[type="text"],
input[type="number"],
input[type="date"] {
    display: block;
    width: 100%;
    padding: 8px 12px;
    margin-bottom: 8px;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 0.9rem;
}

input:focus {
    outline: none;
    border-color: #3498db;
    box-shadow: 0 0 3px rgba(52, 152, 219, 0.3);
}

label {
    display: block;
    font-size: 0.85rem;
    color: #555;
    margin-bottom: 4px;
    margin-top: 8px;
}

What does this code do?

  • The attribute selectors target text, number, and date inputs all at once, giving them consistent padding and rounded corners.
  • input:focus replaces the browser's default focus outline with a subtle blue glow, which matches the Add Item button colour.
  • Save style.css and refresh your browser.
  • Click into any input field. You should see a clean bordered box with a blue glow on focus.

Inputs still look plain?

  • Verify there are no typos in the attribute selectors. The square brackets and quotes matter: input[type="text"].
  • Make sure you saved the file. The dot on the tab in VS Code disappears when the file is saved.

Why aren't my input styles showing up?

  • Add the table styles below the label block:
table {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: 20px;
}

thead {
    background-color: #2c3e50;
    color: #fff;
}

th {
    padding: 12px;
    text-align: left;
    font-weight: 600;
    font-size: 0.85rem;
    text-transform: uppercase;
}

td {
    padding: 8px;
    border-bottom: 1px solid #eee;
}

td input {
    margin-bottom: 0;
}

.item-total {
    font-weight: 600;
    padding: 8px 12px;
}

What does this code do?

  • thead gets a dark navy background so the column headers pop against the white page.
  • border-collapse: collapse removes the double-border gap that tables have by default.
  • td input { margin-bottom: 0 } overrides the general input margin so inputs sit flush inside table cells.
  • Save style.css and refresh your browser.
  • The line items table now has a dark navy header with white uppercase text and clean row separators.

Table header not dark?

  • Check that your HTML has <thead> wrapping the header row. Without <thead>, the CSS selector has nothing to target.
  • Make sure there is no typo in the colour value #2c3e50. Missing a digit turns it into an invalid colour.

Why is my table header not getting the dark background?

  • Add the button styles below the .item-total block. Each button gets a distinct colour so the user knows its purpose at a glance:
.remove-row {
    background-color: #e74c3c;
    color: #fff;
    border: none;
    padding: 6px 12px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 0.8rem;
}

.remove-row:hover {
    background-color: #c0392b;
}

#add-item {
    background-color: #3498db;
    color: #fff;
    border: none;
    padding: 10px 20px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 0.9rem;
    margin-bottom: 30px;
}

#add-item:hover {
    background-color: #2980b9;
}

What does this code do?

  • Red (#e74c3c) signals danger for the Remove button. Blue (#3498db) signals a neutral action for Add Item.
  • The :hover selectors darken each button when the mouse hovers, providing visual feedback that the button is clickable.
  • Now add the totals section layout and the Generate Invoice button below the #add-item:hover block:
.totals {
    margin-left: auto;
    width: 300px;
    margin-bottom: 30px;
}

.totals-row {
    display: flex;
    justify-content: space-between;
    padding: 8px 0;
    border-bottom: 1px solid #eee;
}

.total-final {
    font-weight: 700;
    font-size: 1.2rem;
    border-bottom: 2px solid #2c3e50;
    border-top: 2px solid #2c3e50;
}

#generate-btn {
    background-color: #27ae60;
    color: #fff;
    border: none;
    padding: 14px 30px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 1rem;
    font-weight: 600;
    width: 100%;
}

#generate-btn:hover {
    background-color: #219a52;
}

What does this code do?

  • margin-left: auto pushes the totals section to the right side of the page, like a real invoice.
  • .total-final gets heavy borders top and bottom to make the grand total visually prominent.
  • Green (#27ae60) for the Generate button signals the primary positive action.
  • Save style.css and refresh your browser.
  • You should see a red Remove button, a blue Add Item button, and a large green Generate Invoice button. The totals sit right-aligned below the table.

Buttons not showing colours?

  • Check that #add-item and #generate-btn use the hash symbol. These are ID selectors, not class selectors.
  • Confirm the IDs in your CSS match id="add-item" and id="generate-btn" exactly in index.html.

Why aren't my button colours appearing?

The form view is looking professional. The last set of styles prepares the output section (currently hidden) and print-specific rules for when the invoice is generated later.

  • Add the hidden utility class and output section styles below the #generate-btn:hover block:
.hidden {
    display: none;
}

.output-header {
    display: flex;
    justify-content: space-between;
    margin-bottom: 30px;
    padding-bottom: 20px;
    border-bottom: 2px solid #2c3e50;
}

.output-invoice-info h2 {
    font-size: 2rem;
    color: #2c3e50;
}

.output-client {
    margin-bottom: 30px;
}

.output-table {
    margin-bottom: 30px;
}

.output-table td {
    padding: 12px;
}

.output-totals {
    margin-left: auto;
    width: 300px;
    margin-bottom: 30px;
}

What does this code do?

  • .hidden { display: none } is a utility class that hides the output section until JavaScript removes it.
  • The output styles mirror the form layout (flexbox header, right-aligned totals) so the generated invoice looks consistent.
  • Add the output action buttons and their hover states below the .output-totals block:
.output-actions {
    display: flex;
    gap: 10px;
}

#edit-btn {
    background-color: #3498db;
    color: #fff;
    border: none;
    padding: 10px 20px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 0.9rem;
}

#edit-btn:hover {
    background-color: #2980b9;
}

#print-btn {
    background-color: #27ae60;
    color: #fff;
    border: none;
    padding: 10px 20px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 0.9rem;
}

#print-btn:hover {
    background-color: #219a52;
}

What does this code do?

These style the Edit and Print buttons that appear on the generated invoice. Blue for Edit (go back to editing) and green for Print (final output action).

  • Save style.css and refresh your browser.
  • The form view looks the same because the output section is hidden. To confirm the styles loaded, right-click anywhere on the page, select Inspect, find the #invoice-output element, click the .cls button in the Styles pane, and uncheck hidden. You should see the output section appear with styled headings and a border below the header. Re-check hidden to hide it again.

Output section looks unstyled in DevTools?

  • Make sure your CSS selector matches the HTML exactly. The output header uses class output-header (with a hyphen), not outputHeader.
  • Check that .hidden is defined with display: none. If this class doesn't work, the output section will always be visible.

Why doesn't my output section look right when I toggle the hidden class?

  • Finally, add the print media query below the #print-btn:hover block. This ensures the invoice prints cleanly without buttons or shadows:
@media print {
    body {
        background-color: #fff;
    }

    .container {
        box-shadow: none;
        margin: 0;
        padding: 20px;
        max-width: 100%;
    }

    .no-print {
        display: none;
    }

    .output-table {
        border: 1px solid #ddd;
    }

    .output-table th,
    .output-table td {
        border: 1px solid #ddd;
    }
}

What does this code do?

  • @media print rules only apply when the page is printed or shown in print preview.
  • The shadow and background are removed so the invoice prints on clean white paper.
  • .no-print { display: none } hides the Edit and Print buttons from the printed page.
  • Save style.css.
  • Open print preview with Cmd+P (macOS) or Ctrl+P (Windows). The preview should show a white background with no card shadow, and the invoice fills the full page width. Close the print dialog when done.

Print preview still shows the grey background?

  • Check that @media print is at the very bottom of your CSS file, outside all other selectors. It must not be nested inside another rule.
  • Make sure the closing brace } for the media query matches the opening one. Count your braces carefully.

Why are my print styles not applying in print preview?

✔️ Awesome, I've got everything!

Great. Double check you have saved style.css and all sections are present.

ⓧ I'd like to double check the full code

Scroll up through your style.css and confirm each of these sections is present, in this order:

  • CSS reset (* selector) + body + .container.
  • Heading styles (h1, h2, h3) + .invoice-header Flexbox + .company-details, .invoice-details, .client-details.
  • Input styles (input[type] selectors) + input:focus + label.
  • Table styles (table, thead, th, td, td input, .item-total).
  • Button styles (.remove-row, #add-item, #generate-btn with hover states).
  • Totals section (.totals, .totals-row, .total-final).
  • Hidden class + output section styles (.hidden, .output-header, .output-client, .output-table, .output-totals).
  • Output action buttons (.output-actions, #edit-btn, #print-btn with hover states).
  • @media print block at the very end.
Test the designed obstacle, then commit and push

Your invoice now looks polished with a professional card layout, coloured buttons, and a dark table header. Time to see if it actually works.

  • Refresh Chrome so you are looking at the styled invoice form.
  • Before you type anything, predict: if you enter 2 in the Quantity field and 50 in the Unit Price field, what do you think will happen to the Total column and the Subtotal at the bottom?
  • Now type 2 in Quantity and 50 in Unit Price.

Nothing happens. The line total still shows 0.00 and the subtotal, tax, and total all remain at 0.00. The form looks professional but is completely dead.

Why doesn't anything calculate?

CSS controls appearance only. It can make a button green or centre a card, but it cannot read input values, do arithmetic, or update the page in response to user actions. That is JavaScript's job, and it is exactly what you will add in the next step.

  • The styling is complete. Stage, commit, and push your changes by running these commands in the VS Code terminal:
git add .
git commit -m "feat: add CSS styling with flexbox layout and print styles"
git push
  • Go to your repository page on GitHub and refresh it. You should see the latest commit message feat: add CSS styling with flexbox layout and print styles displayed next to style.css.

Push rejected or authentication error?

  • If Git asks for credentials, your browser should open a GitHub authentication page. Sign in and authorise. Git Credential Manager will save this for future pushes.
  • If you see a "rejected" error, run git pull --rebase first, then try git push again.

Why is my git push being rejected?

Your invoice looks like something a client would actually trust. But it cannot do anything yet. In the next step, you will bring it to life with JavaScript so quantities multiply by prices, totals calculate automatically, and rows appear and disappear on command.

Add JavaScript for Dynamic Calculations

Your invoice looks polished. The layout is clean, buttons are color-coded, and the print styles are ready. But try typing a quantity and price into the first row. Nothing happens. The form is completely dead.

JavaScript is what transforms a static page into an interactive application. In this step, you will write functions that calculate totals in real time, let you add new line items, and remove ones you do not need.

In this step, get ready to:
  • Write a function that calculates line totals, subtotal, tax, and grand total automatically.
  • Add the ability to create new line item rows and remove existing ones.
  • Commit and push your working JavaScript to GitHub.
Write the calculateTotals function

Every time a quantity or price changes, the invoice needs to recalculate every line total and update the summary at the bottom. You will write a single function that handles all of this math.

  • Open script.js in VS Code (it is currently empty).
  • Add the DOM variable declarations and the calculateTotals function by pasting this code:
// Get references to DOM elements we'll use throughout the script
var lineItemsBody = document.querySelector('#line-items tbody');
var addItemBtn = document.getElementById('add-item');
var generateBtn = document.getElementById('generate-btn');
var invoiceForm = document.getElementById('invoice-form');
var invoiceOutput = document.getElementById('invoice-output');
var editBtn = document.getElementById('edit-btn');
var printBtn = document.getElementById('print-btn');

// Loop through all table rows, calculate each line total, and update the summary
function calculateTotals() {
    var rows = lineItemsBody.querySelectorAll('tr');
    var subtotal = 0;

    for (var i = 0; i < rows.length; i++) {
        var quantity = parseFloat(rows[i].querySelector('.item-quantity').value) || 0;
        var price = parseFloat(rows[i].querySelector('.item-price').value) || 0;
        var lineTotal = quantity * price;
        rows[i].querySelector('.item-total').textContent = lineTotal.toFixed(2);
        subtotal += lineTotal;
    }

    var tax = subtotal * 0.10;
    var total = subtotal + tax;

    document.getElementById('subtotal').textContent = subtotal.toFixed(2);
    document.getElementById('tax').textContent = tax.toFixed(2);
    document.getElementById('total').textContent = total.toFixed(2);
}

What does this code do?

The first block grabs references to every HTML element the script will interact with. Storing them in variables at the top means you only look them up once, not every time you need them.

The calculateTotals function loops through every row in the table body, multiplies quantity by price for each line, and writes the result into that row's Total cell. It then sums all line totals into a subtotal, calculates 10% tax, and updates the three summary spans at the bottom of the form.

  • Save script.js. The function is defined but nothing calls it yet. The next chunk connects it to the page inputs.

Right now the function exists but has no trigger. You need to tell the browser to run it every time a quantity or price input changes. This is done with event delegation.

  • Add the input event listener at the bottom of script.js, below the calculateTotals function:
// Recalculate whenever a quantity or price input changes
lineItemsBody.addEventListener('input', function(event) {
    if (event.target.classList.contains('item-quantity') ||
        event.target.classList.contains('item-price')) {
        calculateTotals();
    }
});

What is event delegation?

Instead of attaching a listener to every individual input, you attach one listener to the table body (the parent). When any child element fires an input event, it bubbles up to the parent. The if statement checks whether the event came from a quantity or price input before acting.

This pattern means new rows added later will automatically work without needing their own listeners.

  • Save script.js.
  • Refresh Chrome. In the first row, type 2 in the Quantity field and 50 in the Unit Price field.
  • The line Total column should show 100.00, Subtotal should show 100.00, Tax should show 10.00, and Total should show 110.00.

Totals not updating?

  • Make sure you saved script.js. Unsaved changes will not run in the browser.
  • Open Chrome DevTools (press F12) and check the Console tab for red error messages. A typo in a variable name or missing parenthesis will appear there.
  • Confirm the event listener is placed below the calculateTotals function, not inside it.

Why aren't my invoice totals updating when I type numbers?

Add and remove line items dynamically

The calculation works for the single existing row. But a real invoice has multiple line items. You need functions to create new rows on demand and remove ones the user does not want.

  • Add the following code to script.js, between the calculateTotals function and the lineItemsBody.addEventListener('input' line:
// Build a new table row with the same structure as the starter row
function createRow() {
    var row = document.createElement('tr');
    row.innerHTML =
        '<td><input type="text" class="item-description" placeholder="Item description"></td>' +
        '<td><input type="number" class="item-quantity" min="0" value="0"></td>' +
        '<td><input type="number" class="item-price" min="0" step="0.01" value="0"></td>' +
        '<td class="item-total">0.00</td>' +
        '<td><button class="remove-row" type="button">Remove</button></td>';
    return row;
}

// Remove a row when its Remove button is clicked (keep at least one row)
function handleRemoveRow(event) {
    if (event.target.classList.contains('remove-row')) {
        var rows = lineItemsBody.querySelectorAll('tr');
        if (rows.length > 1) {
            event.target.closest('tr').remove();
            calculateTotals();
        }
    }
}

What does this code do?

  • createRow builds a brand new <tr> element with the same inputs as the starter row in your HTML. It returns the row so you can append it anywhere.
  • handleRemoveRow checks if the clicked element has the class remove-row. If it does and there is more than one row left, it removes that row and recalculates.
  • The rows.length > 1 check prevents the user from deleting the last row, so there is always at least one line item visible.

Now you need to wire these functions to button clicks. The Add Item button creates a row, and any Remove button inside the table triggers removal.

  • Add the following event listeners at the bottom of script.js, below the existing lineItemsBody.addEventListener('input' block:
// Wire up the Add Item button to insert a new row
addItemBtn.addEventListener('click', function() {
    var newRow = createRow();
    lineItemsBody.appendChild(newRow);
});

// Delegate click events on the table body to handle Remove buttons
lineItemsBody.addEventListener('click', handleRemoveRow);

Why delegate the click instead of attaching to each button?

New rows are created dynamically after the page loads. If you attached a click listener directly to each Remove button, newly created rows would not have one. By delegating to the table body, every current and future Remove button is covered automatically.

  • Save script.js and refresh Chrome.
  • Click the blue Add Item button. A new empty row should appear in the table.
  • Type values into the new row's Quantity and Unit Price fields. The totals should recalculate to include the new line item.
  • Click the red Remove button on any row. That row disappears and the totals update instantly.

Add Item button not working?

  • Check that createRow and handleRemoveRow are placed between calculateTotals and the event listeners at the bottom. Functions must be defined before the listeners that call them.
  • Open the Console tab in DevTools (F12) and look for a red error. A missing quote or mismatched parenthesis in the row.innerHTML string is the most common issue.
  • If Remove does nothing, confirm the button in createRow has the class remove-row (with a hyphen, not an underscore).

My Add Item or Remove buttons are not responding. Help me figure out why.

Test and push to GitHub

✔️ Awesome, I've got everything!

Great. Double check you have saved script.js and confirmed all three features work: calculations update on input, Add Item creates rows, and Remove deletes them.

ⓧ I'd like to double check the full code

Scroll up and confirm your script.js contains these sections in order:

  • Seven var declarations at the top (lineItemsBody through printBtn).
  • The createRow function.
  • The calculateTotals function.
  • The handleRemoveRow function.
  • The addItemBtn.addEventListener('click' block.
  • The lineItemsBody.addEventListener('input' block.
  • The lineItemsBody.addEventListener('click', handleRemoveRow) line.

Before you run this, what do you expect to see when you type a quantity of 2 and a price of 50 in the first row?

  • Refresh Chrome. Type 2 in Quantity and 50 in Unit Price.

The line Total shows 100.00, Subtotal shows 100.00, Tax shows 10.00, and Total shows 110.00. Click Add Item to see a new row appear. Click Remove on any row and watch the totals recalculate instantly.

  • Stage, commit, and push your JavaScript by running these commands in the VS Code terminal:
git add .
git commit -m "feat: add dynamic line items and auto-calculation"
git push

What does this commit message mean?

The feat: prefix signals that this commit adds a new feature (as opposed to a bug fix or refactor). Professional teams use this convention so anyone reading the log instantly knows what each commit did.

  • Go to your repository page on GitHub and refresh. You should see script.js listed with the commit message feat: add dynamic line items and auto-calculation displayed.

Push rejected or authentication error?

  • If you see a prompt asking for your username and password, the password field expects a personal access token, not your GitHub password.
  • If the push was rejected, run git pull --rebase first, then try git push again.

I am getting an authentication or push error when trying to push to GitHub. How do I fix this?

Your invoice is alive. Numbers calculate, rows appear and disappear, and totals update in real time. Next up, you will add a Generate Invoice feature that transforms this editable form into a clean, printable document.

Generate and Print the Final Invoice

Your invoice calculates totals beautifully, but a client needs a clean document to review. Right now they would see editable form fields, remove buttons, and the "Add Item" button on screen.

A "Generate Invoice" feature transforms your editable form into a polished, read-only document. The output section is already in your HTML (the hidden #invoice-output div from Step 2). You just need the JavaScript to fill it with data and switch views.

In this step, get ready to:
  • Write a generateInvoice function that populates the read-only output view.
  • Wire up Edit and Print buttons for navigation and printing.
  • Tag your first release and push to GitHub.
Write the generateInvoice function

The generateInvoice function has one job: read every form field value, populate the corresponding element in the output section, and swap which view is visible. You will build it in two parts.

  • In script.js, add the first part of the generateInvoice function directly below the closing brace of handleRemoveRow. Copy the following code:
function generateInvoice() {
    // Populate company, invoice, and client details in the output view
    document.getElementById('output-company-name').textContent =
        document.getElementById('company-name').value || 'Company Name';
    document.getElementById('output-company-address').textContent =
        document.getElementById('company-address').value || '';
    document.getElementById('output-company-email').textContent =
        document.getElementById('company-email').value || '';
    document.getElementById('output-invoice-number').textContent =
        document.getElementById('invoice-number').value || 'INV-001';
    document.getElementById('output-invoice-date').textContent =
        document.getElementById('invoice-date').value || new Date().toLocaleDateString();
    document.getElementById('output-client-name').textContent =
        document.getElementById('client-name').value || 'Client Name';
    document.getElementById('output-client-address').textContent =
        document.getElementById('client-address').value || '';
    document.getElementById('output-client-email').textContent =
        document.getElementById('client-email').value || '';

What does this code do?

Each line reads a value from a form input (like company-name) and writes it into the matching output element (like output-company-name).

The || 'Company Name' part provides a fallback value if the learner left that field empty. This prevents the generated invoice from showing blank spaces.

  • Immediately below the last line you just pasted (the client-email assignment), add the second part of the function that builds the line items table, copies totals, and switches views:
    // Build output table rows from line items
    var outputItems = document.getElementById('output-items');
    outputItems.innerHTML = '';
    var rows = lineItemsBody.querySelectorAll('tr');
    for (var i = 0; i < rows.length; i++) {
        var description = rows[i].querySelector('.item-description').value;
        var quantity = rows[i].querySelector('.item-quantity').value;
        var price = rows[i].querySelector('.item-price').value;
        var lineTotal = rows[i].querySelector('.item-total').textContent;
        if (description || parseFloat(quantity) > 0) {
            var outputRow = document.createElement('tr');
            outputRow.innerHTML =
                '<td>' + description + '</td>' +
                '<td>' + quantity + '</td>' +
                '<td>' + parseFloat(price).toFixed(2) + '</td>' +
                '<td>' + lineTotal + '</td>';
            outputItems.appendChild(outputRow);
        }
    }

    // Copy totals and switch to the output view
    document.getElementById('output-subtotal').textContent =
        document.getElementById('subtotal').textContent;
    document.getElementById('output-tax').textContent =
        document.getElementById('tax').textContent;
    document.getElementById('output-total').textContent =
        document.getElementById('total').textContent;
    invoiceForm.classList.add('hidden');
    invoiceOutput.classList.remove('hidden');
}

generateBtn.addEventListener('click', generateInvoice);

What does the second half do?

  • The for loop reads each line item row, and if it has a description or a non-zero quantity, it creates a new read-only table row in the output section.
  • The totals section copies the already-calculated subtotal, tax, and total from the form view into the output view.
  • invoiceForm.classList.add('hidden') hides the editable form, and invoiceOutput.classList.remove('hidden') reveals the clean output.
  • The last line connects the Generate Invoice button to the function so clicking it triggers the view switch.
  • Save script.js.
  • Switch to Chrome and refresh the page.
  • Type NextWork LLC in the company name field.
  • Type Test Client in the client name field.
  • Set the first line item description to Web Design, quantity to 2, and price to 50.
  • Click Generate Invoice.

The form disappears and a clean, read-only invoice appears. You should see "NextWork LLC" at the top left, "Test Client" under Bill To, and a single line item showing Web Design with a total of 100.00.

Nothing happens when you click Generate Invoice?

  • Open Chrome DevTools (press F12) and check the Console tab for red error messages. A typo in an element ID causes a "Cannot read properties of null" error.
  • Make sure the generateBtn.addEventListener('click', generateInvoice); line is outside and below the closing brace of the generateInvoice function, not inside it.
  • Confirm that the function's opening brace { and closing brace } are balanced. A missing brace is the most common syntax error here.

Why isn't my Generate Invoice button working?

Wire up the Edit and Print buttons

You can generate the invoice, but there is no way to go back to editing or print the document. The Edit and Print buttons already exist in the HTML output section. You just need to wire them up.

  • In script.js, scroll to the bottom of the file. Below the generateBtn listener you just added, add the Edit and Print button listeners:
editBtn.addEventListener('click', function() {
    invoiceOutput.classList.add('hidden');
    invoiceForm.classList.remove('hidden');
});

printBtn.addEventListener('click', function() {
    window.print();
});

What does this code do?

  • The Edit listener reverses the view switch. It hides the output and shows the form again so you can make changes.
  • The Print listener calls window.print(), which opens the browser's native print dialog. The @media print CSS rules from Step 3 automatically hide the buttons during printing.
  • Save script.js and refresh Chrome.
  • Fill in some test data again and click Generate Invoice.
  • Click Edit. The form reappears with your data still intact.
  • Click Generate Invoice again, then click Print. Chrome's print dialog opens showing a professional invoice with no buttons or form elements visible.

Print preview still shows buttons?

  • Check that your style.css has the @media print block with .no-print { display: none; } inside it. This class is applied to the output-actions div in your HTML.

Why are buttons still showing in my print preview?

✔️ Awesome, I've got everything!

Great. Double check you have saved script.js and that all three features work: Generate, Edit, and Print.

ⓧ I'd like to double check the full code

Scroll up through script.js and confirm these sections exist in this order:

  • Seven var declarations at the top (lineItemsBody through printBtn).
  • createRow() function.
  • calculateTotals() function.
  • handleRemoveRow() function.
  • generateInvoice() function (populates fields, builds line item rows, copies totals, switches views).
  • Event listeners: addItemBtn click, lineItemsBody input, lineItemsBody click, generateBtn click, editBtn click, printBtn click.
Tag the release and push to GitHub

Your invoice generator is fully functional. This is a milestone worth marking. Git tags let you label a specific commit as a named release, so you can always come back to this exact working version.

  • Stage and commit your changes by running these commands in the VS Code terminal:
git add .
git commit -m "feat: add invoice generation and print functionality"
  • Create an annotated tag marking this as version 1.0.0 by running:
git tag -a v1.0.0 -m "Release v1.0.0 - working invoice generator"

What is a Git tag?

A tag is a permanent label on a specific commit. Unlike branch names that move forward with new commits, a tag stays fixed. Teams use tags to mark releases like v1.0.0 so they can always find the exact code that shipped.

  • Push both the commit and the tag to GitHub by running:
git push
git push origin v1.0.0

Before you check GitHub, what do you think you will see listed on the repository page?

  • Now see the full story of your project by running:
git log --oneline

You should see your clean commit history telling the story of your build. Each message describes a single accomplishment, from the starter files all the way through to invoice generation.

  • Switch to your browser and go to your repository page on github.com.
  • Look for the Tags section on the right sidebar. You should see v1.0.0 listed there.

Don't see the tag on GitHub?

  • Tags need to be pushed separately from commits. Make sure you ran git push origin v1.0.0 after git push. A regular git push only sends commits, not tags.
  • Refresh the GitHub page. Sometimes the sidebar takes a moment to update.

Why isn't my Git tag showing up on GitHub?

Your invoice generator is complete and published on GitHub with a clean commit history and a tagged release. Next up, you will take on the Secret Mission to add localStorage so your invoices survive page refreshes.

Secret mission

Save Invoice Drafts with localStorage

What happens if you accidentally close the browser while filling in an invoice? All your data disappears. In this secret mission, you'll add localStorage persistence so drafts survive page refreshes and you never lose your work.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally in your browser and is stored free on GitHub. There are no ongoing costs.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Local project folder: invoice-generator on your Desktop.
  • GitHub repository: invoice-generator.
  • Browser localStorage data: the invoiceDraft key (if you completed the Secret Mission).

Keep everything running

No action needed. Choose this if you are still actively building or want to keep the project as a portfolio piece.

  • Your GitHub repository is free and always accessible at your repository URL.
  • Your local files stay on your Desktop and take up minimal disk space.
  • The invoiceDraft localStorage entry persists in Chrome until you manually clear it.

Pause - I'll come back to this later

Free up browser storage while keeping your files and repository intact for later.

  • Clear your localStorage draft by opening the invoice in Chrome and clicking Clear Draft.
  • Your local files and GitHub repository remain untouched and ready when you return.

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Delete the local project folder. On macOS/Linux, run:
cd ~/Desktop
rm -rf invoice-generator
  • On Windows, run:
cd ~/Desktop
rmdir /s /q invoice-generator
  • Delete the GitHub repository. Go to your invoice-generator repository on GitHub.
  • Click Settings (top menu bar of the repository).
  • Scroll to the bottom to the Danger Zone section.
  • Click Delete this repository and follow the confirmation prompts.
  • Clear localStorage data. Open Chrome DevTools (press Cmd+Option+I on macOS or Ctrl+Shift+I on Windows).
  • Go to the Application tab, expand Local Storage, select your site, find the invoiceDraft key, and delete it.

Nice Work!

Nice Work!

You just built a fully functional invoice generator from scratch and shipped it to GitHub with a clean commit history telling the story of every feature you added.

You learned how to:

  • Built an interactive web application using HTML forms, CSS Flexbox layout with print styles, and JavaScript DOM manipulation for dynamic calculations, row management, and view switching.
  • Used event delegation to handle dynamically created elements, letting users add and remove line items with totals recalculating instantly.
  • Mastered the core Git workflow (git add, git commit, git push, git tag) and pushed a real project to GitHub with meaningful commit messages that document your build process.
  • Secret Mission: Added browser-based persistence with localStorage so invoice drafts survive page refreshes and accidental tab closures.

Ready to quiz yourself?