W↓
All docs
🔑
Sign Up/Sign In
docs.solidjs.com/guides/
Public Link
Apr 8, 2025, 10:09:07 AM - complete - 93.3 kB
Starting URLs:
https://docs.solidjs.com/guides/styling-your-components
Crawl Prefixes:
https://docs.solidjs.com/guides/
## Page: https://docs.solidjs.com/guides/styling-your-components Solid provides flexible and versatile ways to style your components. `class` and `style` bindings can both be added to dynamically style components with plain CSS. Solid also supports a range of styling methods - from traditional CSS preprocessors to modern CSS-in-JS solutions - ensuring the flexibility to choose the best approach for your projects. * * * SASS LESS * * * * * * CSS-in-JS is a modern approach to styling components. Within the Solid ecosystem, there are various libraries and solutions available for working with CSS-in-JS, including but not limited to: * Solid Styled Components * Solid Styled JSX CSS-in-JS libraries often come with their own set of APIs and methods for defining, updating, and applying styles dynamically. Many also offer features like theming, media queries, and server-side rendering support right out of the box. **Note:** Before choosing a CSS-in-JS library, it is recommended to check its compatibility with Solid. ### Macaron * * * CSS frameworks provide pre-styled components and utility classes to speed up development. --- ## Page: https://docs.solidjs.com/guides/state-management State management is the process of handling and manipulating data that affects the behavior and presentation of a web application. To build interactive and dynamic web applications, state management is a critical aspect of development. Within Solid, state management is facilitated through the use of reactive primitives. These state management concepts will be shown using a basic counter example: import { createSignal } from "solid-js";function Counter() { const [count, setCount] = createSignal(0); const increment = () => { setCount((prev) => prev + 1); }; return ( <> <div>Current count: {count()}</div> <button onClick={increment}>Increment</button> </> );} There are 3 elements to state management: 1. **State (`count`)**: The _data_ that is used to determine what content to display to the user. 2. **View (`<div>{count()}</div>`)**: The _visual representation_ of the state to the user. 3. **Actions (`increment`)**: Any event that _modifies_ the state. These elements work together to create a "one way data flow". When actions modify the state, the view is updated to show the current state to the user. One way data flow simplifies the management of data and user interactions, which provides a more predictable and maintainable application. * * * State is the source of truth for the application, and is used to determine what content to display to the user. State is represented by a signal, which is a reactive primitive that manages state and notifies the UI of any changes. To create a piece of state, you use the `createSignal` function and pass in the initial value of the state: import { createSignal } from "solid-js";const [count, setCount] = createSignal(0); To access the current value of the state, you call the signal's getter function: console.log(count()); // 0 To update the state, you use the signal's setter function: setCount((prev) => prev + 1);console.log(count()); // 1 With signals, you can create and manage state in a simple and straightforward manner. This allows you to focus on the logic of your application, rather than the complexities of state management. Additionally, signals are reactive, which means as long as it is accessed within a tracking scope, it will always be up to date. * * * To achieve a dynamic user interface, the UI must be able to reflect the current state of the data. The UI is the visual representation of the state to the user, and is rendered using JSX. JSX provides a tracking scope, which keeps the view in sync with the state. Revisiting the `Counter` component presented earlier, rendering the current state of `count` is done within the return body using JSX: return ( <> <div>Current count: {count()}</div> <button onClick={increment}>Increment</button> </>); To render the current state of `count`, the JSX expression `{count()}` is used. The curly braces indicate that the expression is a JavaScript expression, and the parentheses indicate that it is a function call. This expression is representative of a getter function for `count` and will retrieve the current state value. When the state is updated, the UI will be re-rendered to reflect the new state value. Components in Solid only run once upon their initialization. After this initial render, if any changes are made to the state, only the portion of the DOM that is directly associated with the signal change will be updated. The ability to update only the relevant portions of the DOM is a key feature of Solid that allows for performant and efficient UI updates. This is known as fine-grained reactivity. Through reducing the re-rendering of entire components or larger DOM segments, UI will remain more efficient and responsive for the user. * * * When the state is updated, any updates are reflected in the UI. However, there may be times when you want to perform additional actions when the state changes. For example, in the `Counter` component, you may want to display the doubled value of `count` to the user. This can be achieved through the use of effects, which are reactive primitives that perform side effects when the state changes: import { createSignal, createEffect } from "solid-js";function Counter() { const [count, setCount] = createSignal(0); const [doubleCount, setDoubleCount] = createSignal(0); // Initialize a new state for doubleCount const increment = () => { setCount((prev) => prev + 1); }; createEffect(() => { setDoubleCount(count() * 2); // Update doubleCount whenever count changes }); return ( <> <div>Current count: {count()}</div> <div>Doubled count: {doubleCount()}</div> // Display the doubled count <button onClick={increment}>Increment</button> </> );} The `createEffect` function sets up a function to perform side effects whenever the state is modified. Here, a side-effect refers to operations or updates that affect state outside of the local environment - like modifying a global variable or updating the DOM - triggered by those state changes. In the `Counter` component, a `createEffect` function can be used to update the `doubleCount` state whenever the `count` state changes. This keeps the `doubleCount` state in sync with the `count` state, and allows the UI to display the doubled value of `count` to the user. View this example of `doubleCount` in a `createEffect` in the Solid Playground example. Current count: 0 Doubled count: 0 Current count: 1 Doubled count: 2 * * * When you want to calculate new state values based on existing state values, you can use derived state. This is a useful pattern when you want to display a transformation of a state value to the user, but do not want to modify the original state value or create a new state value. Derived values can be created using a signal within a function, which can be referred to as a derived signal. This approach can be used to simplify the `doubleCount` example above, where the additional signal and effect can be replaced with a derived signal: import { createSignal } from "solid-js"function Counter() { const [count, setCount] = createSignal(0); const [doubleCount, setDoubleCount] = createSignal(0); const increment = () => { setCount((prev) => prev + 1); }; createEffect(() => { setDoubleCount(count() * 2); // Update doubleCount whenever count changes }); const doubleCount = () => count() * 2 return ( <> <div>Current count: {count()}</div> <div>Doubled count: {doubleCount()}</div> <button onClick={increment}>Increment</button> </> );} While this approach works for simple use cases, if `doubleCount` is used several times within a component or contains a computationally expensive calculation, it can lead to performance issues. The derived signal would be re-evaluated not just each time `count` is changed, but also for each use of `doubleCount()`. import { createSignal } from "solid-js"function Counter() { const [count, setCount] = createSignal(0) const increment = () => { setCount(count() + 1) } const doubleCount = () => count() * 2 const doubleCount = () => { console.log('doubleCount called') return count() * 2 } return ( <> <div>Current count: {count()}</div> <div>Doubled count: {doubleCount()}</div> <div>Doubled count: {doubleCount()}</div> <div>Doubled count: {doubleCount()}</div> <button onClick={increment}>Increment</button> </> )} doubleCount calleddoubleCount calleddoubleCount called For cases like this, you can use Memos to store the value of `doubleCount`, which are also referred to as a memoized or cached value. When using a memo, the calculation will only run **once** when the value of `count` changes and can be accessed multiple times without re-evaluating for each additional use. Using the `createMemo` function, you can create a memoized value: import { createSignal, createMemo } from "solid-js"function Counter() { const [count, setCount] = createSignal(0) const increment = () => { setCount((prev) => prev + 1); }; const doubleCount = () => { console.log('doubleCount called') return count() * 2 } const doubleCountMemo = createMemo(() => { console.log('doubleCountMemo called') return count() * 2 }) return ( <> <div>Current count: {count()}</div> <div>Doubled count: {doubleCount()}</div> <div>Doubled count: {doubleCount()}</div> <div>Doubled count: {doubleCount()}</div> <div>Doubled count: {doubleCountMemo()}</div> <div>Doubled count: {doubleCountMemo()}</div> <div>Doubled count: {doubleCountMemo()}</div> <button onClick={increment}>Increment</button> </> );} doubleCountMemo calleddoubleCount calleddoubleCount calleddoubleCount called While accessed multiple times, the `doubleCountMemo` will only re-evaluate and log once. This is different from the derived signal, `doubleCount`, which is re-evaluated for each time it is accessed. View a similar example comparing a derived signal and a memo in the Solid Playground. * * * When you want to share state between components, you can lift state up to a common ancestor component. While state is not tied to components, you may want to link multiple components together in order to access and manipulate the same piece of state. This can keep things synchronized across the component tree and allow for more predictable state management. For example, in the `Counter` component, you may want to display the doubled value of `count` to the user through a separate component: import { createSignal, createEffect, createMemo } from "solid-js";function App() { const [count, setCount] = createSignal(0); const [doubleCount, setDoubleCount] = createSignal(0); const squaredCount = createMemo(() => count() * count()); createEffect(() => { setDoubleCount(count() * 2); }); return ( <> <Counter count={count()} setCount={setCount} /> <DisplayCounts count={count()} doubleCount={doubleCount()} squaredCount={squaredCount()} /> </> );}function Counter(props) { const increment = () => { props.setCount((prev) => prev + 1); }; return <button onClick={increment}>Increment</button>;}function DisplayCounts(props) { return ( <div> <div>Current count: {props.count}</div> <div>Doubled count: {props.doubleCount}</div> <div>Squared count: {props.squaredCount}</div> </div> );}export default App; To share the `count` state between the `Counter` and `DisplayCounts` components, you can lift the state up to the `App` component. This allows the `Counter` and `DisplayCounts` functions to access the same piece of state, but also allows the `Counter` component to update the state through the `setCount` setter function. When sharing state between components, you can access the state through `props`. Props values that are passed down from the parent component are read-only, which means they cannot be directly modified by the child component. However, you can pass down setter functions from the parent component to allow the child component to indirectly modify the parent's state. * * * As applications grow in size and complexity, lifting state can become difficult to manage. To avoid the concept of prop drilling, which is the process of passing props through multiple components, Solid offers stores to manage state in a more scalable and maintainable manner. To learn more about managing complex state, navigate to the complex state management page. --- ## Page: https://docs.solidjs.com/guides/routing-and-navigation Solid Router simplifies routing in Solid applications to help developers manage navigation and rendering by defining routes using JSX or objects passed via props. * * * **1\. Install the router** This package is not included by default. npm i @solidjs/router pnpm i @solidjs/router yarn add @solidjs/router bun i @solidjs/router deno add npm:@solidjs/router **2\. Setup the `<Router>` component** Start your application by rendering the Router component. This component will match the URL to display the desired page. import { render } from "solid-js/web";import { Router } from "@solidjs/router";render(() => <Router />, document.getElementById("root")); **3\. Provide a root level layout** This layout will not update on page change and is the ideal place for top-level navigation and Context Providers. import { render } from "solid-js/web";import { Router } from "@solidjs/router";const App = (props) => ( <> <h1>Site Title</h1> {props.children} </>);render(() => <Router root={App} />, document.getElementById("root")); **4\. Add routes** Each route is added to the `Router` using the `Route` component. Here, you specify a path and a component to render once the user navigates to that path. import { render } from "solid-js/web";import { Router, Route } from "@solidjs/router";import Home from "./pages/Home";import Users from "./pages/Users";const App = (props) => ( <> <h1>Site Title</h1> {props.children} </>);render( () => ( <Router root={App}> <Route path="/" component={Home} /> <Route path="/users" component={Users} /> </Router> ), document.getElementById("root")); **5\. Create a CatchAll route (404 page)** A catchall route can be used for pages not found at any nested level of the router. Using `*` will retrieve the rest of the path. Optionally, you can also add a parameter name. import { render } from "solid-js/web";import { Router, Route } from "@solidjs/router";import Home from "./pages/Home";import Users from "./pages/Users";import NotFound from "./pages/NotFound";const App = (props) => ( <> <h1>Site Title</h1> {props.children} </>);render( () => ( <Router root={App}> <Route path="/" component={Home} /> <Route path="/users" component={Users} /> <Route path="*paramName" component={NotFound} /> </Router> ), document.getElementById("root")); **6\. Create links to your routes** The `<A>` component provides navigation to an application's routes. Alternatively, you can use the native anchor tag. However, the `<A>` component provides additional functionality including properties for CSS, `inactiveClass` and `activeClass`. import { render } from "solid-js/web";import { Router, Route, A } from "@solidjs/router";import Home from "./pages/Home";import Users from "./pages/Users";import NotFound from "./pages/NotFound";const App = (props) => ( <> <nav> <A href="/">Home</A> <A href="/users">Users</A> </nav> <h1>Site Title</h1> {props.children} </>);render( () => ( <Router root={App}> <Route path="/" component={Home} /> <Route path="/users" component={Users} /> <Route path="*paramName" component={NotFound} /> </Router> ), document.getElementById("root")); * * * The `lazy` function postpones the loading of a component until it is navigated to. import { lazy } from "solid-js";import { render } from "solid-js/web";import { Router, Route } from "@solidjs/router";const Users = lazy(() => import("./pages/Users"));const Home = lazy(() => import("./pages/Home"));const App = (props) => ( <> <h1>Site Title</h1> {props.children} </>);render( () => ( <Router root={App}> <Route path="/users" component={Users} /> <Route path="/" component={Home} /> </Router> ), document.getElementById("root")); * * * If a path is unknown ahead of time, you can treat part of the path as a flexible parameter. import { lazy } from "solid-js";import { render } from "solid-js/web";import { Router, Route } from "@solidjs/router";const Users = lazy(() => import("./pages/Users"));const User = lazy(() => import("./pages/User"));const Home = lazy(() => import("./pages/Home"));render( () => ( <Router> <Route path="/users" component={Users} /> <Route path="/users/:id" component={User} /> <Route path="/" component={Home} /> </Router> ), document.getElementById("root")); The colon indicates that `id` can be any string, and as long as the URL fits that pattern, the `<User>` component will show. You can then access that `id` from within a route component with `useParams`. **Note on animation/transitions**: Routes that share the same path will be treated as the same route. If you want to force re-render, you can wrap your component in a keyed `<Show>`: <Show when={params.something} keyed> <MyComponent></Show> ### Accessing parameters In cases where you may need to access a dynamic route's parameters within your components, the `useParams` primitive is available. Once the parameters have been accessed using `useParams`, they can be used within your component: import { useParams } from "@solidjs/router";const User = () => { const params = useParams(); // Retrieve the dynamic route parameters // Now you can access the id parameter as params.id return ( <p> This is the user with the id of <code>{params.id}</code> </p> );}; `useParams` can be especially useful with other Solid primitives, such as `createResource` and `createSignal`, which can create dynamic behaviors based on the route parameters. import { createResource } from "solid-js";import { useParams } from "@solidjs/router";async function fetchUser(id) { const response = await fetch( `https://jsonplaceholder.typicode.com/users/${id}` ); return response.json();}const User = () => { const params = useParams(); const [data] = createResource(() => params.id, fetchUser); // Pass the id parameter to createResource return ( <div> <Show when={!data.loading} fallback={<p>Loading...</p>}> <div> <p>Name: {data().name}</p> <p>Email: {data().email}</p> <p>Phone: {data().phone}</p> </div> </Show> </div> );}; Every time the `id` parameter changes in this example, the `fetchUser` function is called to fetch the new user data. ### Validating routes Each path parameter can be validated using a `MatchFilter`. Instead of checking for the presence of a parameter, this allows for more complex routing descriptions: import { lazy } from "solid-js";import { render } from "solid-js/web";import { Router, Route } from "@solidjs/router";import type { SegmentValidators } from "./types";const User = lazy(() => import("./pages/User"));const filters: MatchFilters = { parent: ["mom", "dad"], // allow enum values id: /^\d+$/, // only allow numbers withHtmlExtension: (v: string) => v.length > 5 && v.endsWith(".html"), // only `*.html` extensions wanted};render(() => ( <Router> <Route path="/users/:parent/:id/:withHtmlExtension" component={User} matchFilters={filters} /> </Router>), document.getElementById("root")); In this example, the `matchFilters` prop provides a way to validate the `parent`, `id` and `withHtmlExtension` parameters against the filters defined in `filters`. If the validation fails, the route will not match. In this example: * `/users/mom/123/contact.html` will match, * `/users/dad/123/about.html` will match, * `/users/aunt/123/contact.html` will **not** match as `:parent` is not 'mom' or 'dad', * `/users/mom/me/contact.html` will **not** match as `:id` is not a number, * `/users/dad/123/contact` will **not** match as `:withHtmlExtension` is missing `.html`. ### Optional parameters Parameters can be specified as optional by adding a question mark to the end of the parameter name: // Matches stories and stories/123 but not stories/123/comments<Route path="/stories/:id?" component={Stories} /> ### Wildcard routes To match any descendent routes within a given path, you can use the wildcard token (`*`). This can be used to represent any value in that segment of the path. // Will match any path beginning with foo (eg. foo/, foo/a/, foo/a/b/c)<Route path="foo/*" component={Foo} /> To expose the wildcard portion to the component as a parameter, you can name it: <Route path="foo/*any" component={Foo} /> Wildcard tokens **must** be the last part of the path; `foo/*any/bar` will not create any routes. ### Multiple paths The `Routes` component also supports defining multiple paths using an array. This avoids a route rerendering when switching between two or more locations that it matches: // Navigating from "/login" to "/register" will not cause the component to re-render<Route path={["login", "register"]} component={Login} /> * * * Only leaf `<Route>` nodes (the innermost `<Route>` components) are given a route. <Route path="/users" component={Users}> <Route path="/:id" component={User} /></Route> The following two route definitions both match the same URL `/users/:id` and render the same component: <Route path="/users/:id" component={User} /><Route path="/users"> <Route path="/:id" component={User} /></Route> If you want to make the parent its own route, you have to specify it separately: <Route path="/users" component={Users} /><Route path="/users/:id" component={User} />// or<Route path="/users"> <Route path="/" component={Users} /> <Route path="/:id" component={User} /></Route> You can also take advantage of nesting by using `props.children` passed to the route component. function PageWrapper(props) { return ( <div> <h1> We love our users! </h1> {props.children} <A href="/">Back Home</A> </div> );}<Route path="/users" component={PageWrapper}> <Route path="/" component={Users} /> <Route path="/:id" component={User} /></Route>; The routes are still configured the same, however now their components will appear inside the parent component where the `props.children` is declared. Routes can also be nested indefinitely. This example will only render the route `/layer1/layer2`, which will be nested in 3 divs. <Route path="/" component={(props) => <div>Outermost layer starts here {props.children}</div>}> <Route path="layer1" component={(props) => <div>Second layer {props.children}</div>} > <Route path="layer2" component={() => <div>Innermost layer</div>} /> </Route></Route> * * * With preload functions, data fetching is started parallel to loading the route, so it can be used as soon as possible. The preload function prevents this by being called once the Route is loaded, or eagerly if links are hovered. As the only argument, the preload function is passed an object that is used to access route information: import { lazy } from "solid-js";import { Route } from "@solidjs/router";const User = lazy(() => import("./pages/users/[id].js"));// preload functionfunction preloadUser({ params, location }) { // do preload} The preload function is then passed in the `<Route>` definition: <Route path="/users/:id" component={User} preload={preloadUser} /> * * * You can export preload functions and data wrappers that correspond to routes from a dedicated `[route].data.js` or `[route].data.ts` file. This pattern provides a way to import the data function without loading anything else. // src/pages/users/[id].data.jsimport { query } from "@solidjs/router";export const getUser = query(async (id) => { return (await fetch(`https://swapi.tech/api/people/${id}/`)).json();}, "getUser");export function preloadUser({ params, location, intent }) { return getUser(params.id);} `preloadUser` is passed an object which contains `params`, `location` and `intent`. Please note that while it is best practice to name these files as `[id].data.js`, you can still name them as `route.data.js`. The value of a preload function is passed to the page component when called at any time other than "preload". This means you can initialize the page, or use Data APIs. import { lazy } from "solid-js";import { render } from "solid-js/web";import { Router, Route } from "@solidjs/router";import { preloadUser } from "./pages/users/[id].data.js";const Home = lazy(() => import("./pages/Home"));const User = lazy(() => import("./pages/users/[id]"));render( () => ( <Router> <Route path="/" component={Home} /> <Route path="/users/:id" component={User} preload={preloadUser} /> </Router> ), document.getElementById("root")); `[id].jsx` contains the component that gets rendered. When you wrap the function within `createAsync` with the imported function, it will yield a signal once the anticipated promise resolves. // [id].jsximport { createAsync } from "@solidjs/router";import { getUser } from "./[id].data";export default function Users(props) { console.log("Users.props", props); const user = createAsync(() => getUser(props.params.id)); return ( <> <h1>User</h1> <div> <pre>{JSON.stringify(user(), null, 2)}</pre> </div> </> );} To learn more about routing your Solid applications, visit the Solid Router documentation. --- ## Page: https://docs.solidjs.com/guides/complex-state-management As applications grow and start to involve many components, more intricate user interactions, and possibly communication with backend services, you may find that staying organized with more basic state management methods can become difficult to maintain. Consider this example: import { For, createSignal, Show, createMemo } from "solid-js"const App = () => { const [tasks, setTasks] = createSignal([]) const [numberOfTasks, setNumberOfTasks] = createSignal(tasks.length) const completedTasks = createMemo(() => tasks().filter((task) => task.completed)) let input const addTask = (text) => { setTasks([...tasks(), { id: tasks().length, text, completed: false }]) setNumberOfTasks(numberOfTasks() + 1) } const toggleTask = (id) => { setTasks( tasks().map((task) => task.id !== id ? task : { ...task, completed: !task.completed } ) ) } return ( <> <h1>My list</h1> <span>You have {numberOfTasks()} task(s) today!</span> <div> <input ref={input} /> <button onClick={(e) => { if (!input.value.trim()) return addTask(input.value) input.value = "" }} > Add Task </button> </div> <For each={tasks()}> {(task) => { const { id, text } = task console.log(`Creating ${text}`) return ( <div> <input type="checkbox" checked={task.completed} onChange={[toggleTask, id]} /> <span style={{ "text-decoration": task.completed ? "line-through" : "none", }} > {text} </span> </div> ) }} </For> </> )}export default App There are several challenges to managing state in this way: * Increased verbosity with the multiple `createSignal` calls for `tasks`, `numberOfTasks`, as well as a `createMemo` function for `completedTasks`. Additionally, with each state update, there requires manual updates to other related states which risks the application becoming out of sync. * While Solid is optimized, this components design leads to frequent recalculations, such as updating `completedTasks` with every toggle action, which can negatively impact performance. In addition, the dependence on the component's logic on the current state for `numberOfTasks` and `completedTasks` can complicate code understanding. As an application like this scales, managing state in this manner becomes even more complex. Introducing other dependent state variables would require updates across the _entire_ component which would likely introduce more errors. This would likely make it more difficult to separate specific functionalities into distinct, reusable components without transferring a substantial portion of state management logic, as well. * * * Through recreating this list using Stores, you will see how stores can improve the readability and management of your code. If you're new to the concept of stores, see the stores section. * * * To reduce the amount of signals that were used in the original example, you can do the following using a store: import { createStore } from "solid-js/store"const App = () => { const [state, setState] = createStore({ tasks: [], numberOfTasks: 0, })}export default App Through using a store, you no longer need to keep track of separate signals for `tasks`, `numberOfTasks`, and `completedTasks`. * * * Once you have created your store, the values can be accessed directly through the first value returned by the `createStore` function: import { createStore } from "solid-js/store"const App = () => { const [state, setState] = createStore({ tasks: [], numberOfTasks: 0, }) return ( <> <h1>My Task List for Today</h1> <span>You have {state.numberOfTasks} task(s) for today!</span> </> )}export default App Through `state.numberOfTasks`, the display will now show the store's value held in the `numberOfTasks` property. * * * When you want to modify your store, you use the second element returned by the `createStore` function. This element allows you to make modifications to the store, letting you both add new properties and update existing ones. However, because properties within a store are created lazily, setting a property in the component function body without creating a reactive scope will **not** update the value. To create the signal so it reactively updates, you have to access the property within a tracking scope, such as using a `createEffect`: // not reactivesetState("numberOfTasks", state.tasks.length)// reactivecreateEffect(() => { setState("numberOfTasks", state.tasks.length)}) ### Adding to an array To add an element to an array, in this case the new task, you can append to the next index of the array through `state.tasks.length`. By pinpointing the `tasks` key in combination with the upcoming position, the new task is added to the end of the array. const addTask = (text) => { setState("tasks", state.tasks.length, { id: state.tasks.length, text, completed: false, })} The setter in stores follow path syntax: `setStore("key", value)`. In the `addTask` function the `tasks` array is appended through `setState("tasks", state.tasks.length, { id: state.tasks.length, text, completed: false })`, an example of this in action. #### Mutating state with `produce` In situations where you need to make multiple `setState` calls and target multiple properties, you can simplify your code and improve readability by using Solid's `produce` utility function. Something such as toggle function: const toggleTask = (id) => { const currentCompletedStatus = state.tasks[id].completed setState( "tasks", (task) => task.id === id, "completed", !currentCompletedStatus )} Can be simplified using `produce`: import { produce } from "solid-js/store"const toggleTask = (id) => { setState( "tasks", (tasks) => tasks.id === id, produce((task) => { task.completed = !task.completed }) )}// You can also rewrite the `addTask` function through produceconst addTask = (text) => { setState( "tasks", produce((task) => { task.push({ id: state.tasks.length, text, completed: false }) }) )} Read about some of the other advantages to using `produce`. The updated example: import { For, createEffect, Show } from "solid-js"import { createStore, produce } from "solid-js/store"const App = () => { let input // lets you target the input value const [state, setState] = createStore({ tasks: [], numberOfTasks: 0, }) const addTask = (text) => { setState("tasks", state.tasks.length, { id: state.tasks.length, text, completed: false, }) } const toggleTask = (id) => { setState( "tasks", (tasks) => tasks.id === id, produce((task) => { task.completed = !task.completed }) ) } createEffect(() => { setState("numberOfTasks", state.tasks.length) }) return ( <> <div> <h1>My Task List for Today</h1> <span>You have {state.numberOfTasks} task(s) for today!</span> </div> <input ref={input} /> <button onClick={(e) => { if (!input.value.trim()) return addTask(input.value) input.value = "" }} > Add Task </button> <For each={state.tasks}> {(task) => { const { id, text } = task return ( <div> <input type="checkbox" checked={task.completed} onChange={() => toggleTask(task.id)} /> <span>{text}</span> </div> ) }} </For> </> )}export default App * * * As applications grow and become more complex, sharing state between components can become a challenge. Passing state and functions from parent to child components, especially across multiple levels, is commonly referred to as "prop drilling". Prop drilling can lead to verbose, hard-to-maintain code, and can make the data flow in an application more difficult to follow. To solve this problem and allow for a more scalable and maintainable codebase, Solid provides context. To use this, you need to create a context. This context will have a default value and can be consumed by any _descendant_ component. import { createContext } from "solid-js"const TaskContext = createContext() Your components will be wrapped with the `Provider` from the context, and passed with the values that you wish to share. import { createStore } from "solid-js/store"const TaskApp = () => { const [state, setState] = createStore({ tasks: [], numberOfTasks: 0, }) return ( <TaskContext.Provider value={{ state, setState }}> {/* Your components */} </TaskContext.Provider> )} In any descendent component, you can consume the context values using `useContext`: import { useContext } from "solid-js"const TaskList = () => { const { state, setState } = useContext(TaskContext) // Now you can use the shared state and functions} For a deeper dive, please refer to our dedicated page on context. --- ## Page: https://docs.solidjs.com/guides/fetching-data For most modern web applications, data fetching is a common task. Solid has a built-in utility, `createResource` , that was created to simplify data fetching. * * * `createResource` is a specialized signal designed specifically for managing asynchronous data fetching. It wraps around the async operations, providing a way to handle various states: loading, success, and error. This function is non-blocking, meaning that `createResource` guarantees that the application remains responsive, even during the retrieval of information. Because of this, common pitfalls of traditional async handling, such as unresponsive UIs during data fetching can be avoided. * * * `createResource` requires a function that returns a promise as its argument. Upon the call, `createResource` returns a signal which has reactive properties like loading, error, latest, etc. These properties can be used to conditionally render JSX based on the current reactive state. The fetcher function that is created makes a call to get a user, which is then passed in as an argument to `createResource`. The signal returned from the `createResource` provides the properties that can assist with conditional rendering based on the current reactive state: * `state`: The current status of the operation (`unresolved`, `pending`, `ready`, `refreshing`, or `errored`). * `loading`: Indicates that the operation is currently in progress via a `boolean`. * `error`: If the operation fails for any reason, this property will contain information about this error. It may be a string with an error message, or an object with more detailed information. * `latest`: The most recent data or result returned from the operation. When there is a change in the source signal, an internal fetch process is triggered to retrieve new data based on this change. import { createSignal, createResource, Switch, Match, Show } from "solid-js";const fetchUser = async (id) => { const response = await fetch(`https://swapi.dev/api/people/${id}/`); return response.json();}function App() { const [userId, setUserId] = createSignal(); const [user] = createResource(userId, fetchUser); return ( <div> <input type="number" min="1" placeholder="Enter Numeric Id" onInput={(e) => setUserId(e.currentTarget.value)} /> <Show when={user.loading}> <p>Loading...</p> </Show> <Switch> <Match when={user.error}> <span>Error: {user.error}</span> </Match> <Match when={user()}> <div>{JSON.stringify(user())}</div> </Match> </Switch> </div> );} Whenever the signal value, `userId`, changes, the internal fetch method `fetchUser` gets triggered. The properties of the `user` resource allow for conditional rendering based on the different states of the fetch process. The `Switch/Match` construct provides one way to manage these conditions. When the fetch succeeds and user data is retrieved, the `user()` condition becomes active, and its related block executes. However, if there's an error while fetching, the `user.error` block becomes `true`, leading to its corresponding `Match` block being shown. In addition to the `error` property, the `loading` property offers a way to display a loading state to the user during the fetch operation. * * * Although you can use `createResource` independently, Solid provides an alternative method for synchronizing the display of multiple asynchronous events. `Suspense` is a component in Solid designed to act as a boundary. It allows you to display a fallback placeholder while waiting for all asynchronous events to resolve, preventing the display of partially loaded content: import { createSignal, createResource, Switch, Match, Suspense } from "solid-js";const fetchUser = async (id) => { const response = await fetch(`https://swapi.dev/api/people/${id}/`); return response.json();}function App() { const [userId, setUserId] = createSignal(); const [user] = createResource(userId, fetchUser); return ( <div> <input type="number" min="1" placeholder="Enter Numeric Id" onInput={(e) => setUserId(e.currentTarget.value)} /> <Suspense fallback={<div>Loading...</div>}> <Switch> <Match when={user.error}> <span>Error: {user.error.message}</span> </Match> <Match when={user()}> <div>{JSON.stringify(user())}</div> </Match> </Switch> </Suspense> </div> );} `Suspense` has the ability to identify asynchronous reads within its descendants and act accordingly. This feature helps to remove any intermediate components that may otherwise be displayed during partial loading states. Additionally, you can nest as many components as needed within `Suspense` but only the closest ancestor will switch to the `fallback` state when a loading state is detected. * * * With the second output of `createResource`, there are 2 powerful methods designed to enhance and simplify some complex aspects of data management: ### `mutate` In situations where immediate feedback or responsiveness is important, the `mutate` method offers "optimistic mutations." These mutations provide instant feedback, even while background processes, such as server confirmations, are still in progress. This functionality is particularly valuable in applications like task lists. For example, when users input a new task and click the `Add` button, the list will refresh immediately, regardless of the ongoing data communication with the server. import { For, createResource } from "solid-js"function TodoList() { const [tasks, { mutate }] = createResource(fetchTasksFromServer); return ( <> <ul> <For each={tasks()}> {(task) => ( <li>{task.name}</li> )} </For> </ul> <button onClick={() => { mutate((todos) => [...todos, "do new task"]); // add todo for user // make a call to send to database }} > Add Task </button> </> );} ### `refetch` When real-time feedback is necessary, the `refetch` method can be used to reload the current query regardless of any changes. This method can be particularly useful when data is constantly evolving, such as with real-time financial applications. import { createResource, onCleanup } from "solid-js"function StockPriceTicker() { const [prices, { refetch }] = createResource(fetchStockPrices); const timer = setInterval(() => { refetch() }, 1000); onCleanup(() => clearInterval(timer))} --- ## Page: https://docs.solidjs.com/guides/testing Testing your Solid applications is important to inspiring confidence in your codebase through preventing regressions. * * * ### Testing packages explanations * `vitest` - testing framework that includes runner, assertion engine, and mocking facilities * `jsdom` - a virtual DOM used to simulate a headless browser environment running in node * `@solidjs/testing-library` - a library to simplify testing components, directives, and primitives, with automatic cleanup * `@testing-library/user-event` - used to simulate user events that are closer to reality * `@testing-library/jest-dom` - augments expect with helpful matchers ### Adding testing packages The recommended testing framework for Solid applications is vitest. To get started with vitest, install the following development dependencies: npm i vitest jsdom @solidjs/testing-library @testing-library/user-event @testing-library/jest-dom -D pnpm i vitest jsdom @solidjs/testing-library @testing-library/user-event @testing-library/jest-dom -D yarn add vitest jsdom @solidjs/testing-library @testing-library/user-event @testing-library/jest-dom -D bun i vitest jsdom @solidjs/testing-library @testing-library/user-event @testing-library/jest-dom -d deno add npm:vitest jsdom @solidjs/testing-library @testing-library/user-event @testing-library/jest-dom -D ### Testing configuration In your `package.json` add a `test` script calling `vitest`: "scripts": { "test": "vitest" } It is not necessary to add `@testing-library/jest-dom` to the testing options in `vite.config`, since `vite-plugin-solid` automatically detects and loads it if present. #### TypeScript configuration If using TypeScript, add `@testing-library/jest-dom` to `tsconfig.json#compilerOptions.types`: "compilerOptions": { // ... "jsx": "preserve", "jsxImportSource": "solid-js", "types": ["vite/client", "@testing-library/jest-dom"] } #### SolidStart configuration When using SolidStart, create a `vitest.config.ts` file: import solid from "vite-plugin-solid"import { defineConfig } from "vitest/config"export default defineConfig({ plugins: [solid()], resolve: { conditions: ["development", "browser"], },}) * * * ### Components testing Testing components involves three main things: * Rendering the component * Interacting with the component * Validating assertions To write tests for your components, create a `[name].test.tsx` file. The purpose of this file is to describe the intended behavior from a user's perspective in the form of unit tests: import { test, expect } from "vitest"import { render } from "@solidjs/testing-library"import userEvent from "@testing-library/user-event"import { Counter } from "./Counter"const user = userEvent.setup()test("increments value", async () => { const { getByRole } = render(() => <Counter />) const counter = getByRole('button') expect(counter).toHaveTextContent("1") await user.click(counter) expect(counter).toHaveTextContent("2")}) export const Counter = () => { const [count, setCount] = createSignal(1); return ( <button onClick={() => setCount(count() + 1)}> {count()} </button> );} In the `test.jsx` file, the `render` call from `@solidjs/testing-library` is used to render the component and supply the props and context. To mimic a user interaction, `@testing-library/user-event` is used. The `expect` function provided by `vitest` is extended with a `.ToHaveTextContent("content")` matcher from `@testing-library/jest-dom` to supply what the expected behavior is for this component. To run this test, use the following command: npm run test pnpm test yarn test bun run test deno run test If running the command is successful, you will get the following result showing whether the tests have passed or failed: [RUN] v1.4.0 solid-app/src/components/Counter.test.tsx ✓ src/components/Counter.test.tsx (1) ✓ <Counter /> (1) ✓ increments value Test Files 1 passed (1) Tests 1 passed (1) Start at 16:51:19 Duration 4.34s (transform 1.01s, setup 205ms, collect 1.54s, tests 155ms,environment 880ms, prepare 212ms) #### Rendering the component The `render` function from `@solidjs/testing-library` creates the testing environment within the `test.tsx` file. It sets up the container, rendering the component within it, and automatically registers it for clean-up after a successful test. Additionally, it manages wrapping the component in contexts as well as setting up a router. const renderResult = render( () => <MyComponent />, // @solidjs/testing-library requires a function { // all options are optional container, // manually set up your own container, will not be handled baseElement, // parent of container in case it is not supplied queries, // manually set up custom queries hydrate, // set to `true` to use hydration wrapper, // reusable wrapper component to supply context location, // sets up a router pointed to the location if provided })const { asFragment, // function returning the contents of the container baseElement, // the parent of the container container, // the container in which the component is rendered debug, // a function giving some helpful debugging output unmount, // manually removing the component from the container ...queries, // functions to select elements from the container} = renderResult ##### Using the right queries Queries are helpers used to find elements within a page. ⎧ Role get ⎫ By ⎪ DisplayValue query ⎬ ⎨ LabelText find ⎭ AllBy ⎪ Text ⎩ ... The prefixes (`get`, `query`, and `find`) and the middle portion (`By` and `AllBy`) depend on if the query should wait for an element to appear (or not), whether it should throw an error if the element cannot be found, and how it should handle multiple matches: * **getBy**: synchronous, throws if not found or more than 1 matches * **getAllBy**: synchronous, throws if not found, returns array of matches * **queryBy**: synchronous, null if not found, error if more than 1 matches * **queryAllBy**: synchronous, returns array of zero or more matches * **findBy**: asynchronous, rejected if not found within 1000ms or more than 1 matches, resolves wth element if found * **findAllBy**: asynchronous, rejected if not found within 1000ms, resolves with array of one or more element(s) By default, queries should start with `get...`. If there are multiple elements matching the same query, `getAllBy...` should be used, otherwise use `getBy...`. There are two exceptions when you should **not** start with `get...`: 1. If the `location` option is used or the component is based on resources, the router will be lazy-loaded; in this case, the first query after rendering needs to be `find...` 2. When testing something that is _not_ rendered, you will need to find something that will be rendered at the same time; after that, use `queryAllBy...` to test if the result is an empty array (`[]`). The query's suffix (Role, LabelText, ...) depends on the characteristics of the element you want to select. If possible, try to select for accessible attributes (roughly in the following order): * **Role**: WAI ARIA landmark roles which are automatically set by semantic elements like `<button>` or otherwise use `role` attribute * **LabelText**: elements that are described by a label wrapping the element, or by an `aria-label` attribute, or is linked with `for`\- or `aria-labelledby` attribute * **PlaceholderText**: input elements with a `placeholder` attribute * **Text**: searches text within all text nodes in the element, even if split over multiple nodes * **DisplayValue**: form elements showing the given value (e.g. select elements) * **AltText**: images with alt text * **Title**: HTML elements with the `title` attribute or SVGs with the `<title>` tag containing the given text * **TestId**: queries by the `data-testid` attribute; a different data attribute can be setup via `configure({testIdAttribute: 'data-my-test-attribute'})`; TestId-queries are _not accessible_, so use them only as a last resort. For more information, check the testing-library documentation. #### Testing through Portal Solid allows components to break through the DOM tree structure using `<Portal>`. This mechanism will still work in testing, so the content of the portals will break out of the testing container. In order to test this content, make sure to use the `screen` export to query the contents: import { test, expect } from "vitest"import { render, screen } from "@solidjs/testing-library"import { Toast } from "./Toast"test("increments value", async () => { render(() => <Toast><p>This is a toast</p></Toast>) const toast = screen.getByRole("log") expect(toast).toHaveTextContent("This is a toast")}) import { Portal } from "solid-js/web";export const Toast = (props) => { return ( <Portal> <div class="toast" role={props.role ?? "log"}> {props.children} </div> </Portal> );} #### Testing in context If a component relies on some context, to wrap it use the `wrapper` option: import { test, expect } from "vitest"import { render } from "@solidjs/testing-library"import { DataContext, DataConsumer } from "./Data"const wrapper = (props) => <DataContext value="test" {...props} />test("receives data from context", () => { const { getByText } = render(() => <DataConsumer />, { wrapper }) expect(getByText("test")).toBeInTheDocument()}); Wrappers can be re-used if they are created externally. For wrappers with different values, a higher-order component creating the required wrappers can make the tests more concise: const createWrapper = (value) => (props) => <DataContext value={value} {...props}/> ##### Testing routes For convenience, the `render` function supports the `location` option that wraps the rendered component in a router pointing at the given location. Since the `<Router>` component is lazily loaded, the first query after rendering needs to be asynchronous, i.e. `findBy...`: const { findByText } = render( () => <Route path="/article/:id" component={Article} />, { location: "/article/12345" });expect(await findByText("Article 12345")).toBeInTheDocument() #### Interacting with components Many components are not static, rather they change based on user interactions. To test these changes, these interactions need to be simulated. To simulate user interactions, `@testing-library/user-event` library can be used. It takes care of the usual order of events as they would occur in actual user interactions. For example, this means that a `click` event from the user would be accompanied by `mousemove`, `hover`, `keydown`, `focus`, `keyup`, and `keypress`. The most convenient events to test are typically `click`, `keyboard` and `pointer` (to simulate touch events). To dive deeper into these events, you can learn about them in the `user-event` documentation. ##### Using timers If you require a fake timer and want to use `vi.useFakeTimers()` in your tests, it must set it up with an `advanceTimers` option: import { vi } from "vitest"const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime })vi.useFakeTimers()describe("pre-login: sign-in", () => { const { getByRole, getByLabelText } = render(() => <User />) const signUp = getByRole('button', { text: 'Sign-in' }) // use convenience API click: user.click(signUp) const name = getByLabelText('Name') // use complex keyboard input: user.keyboard(name, "{Shift}test{Space}{Shift}user") const password = getByLabelText('Password') user.keyboard(name, "secret") const login = getByRole('button', { text: 'Login' }) // use touch event user.pointer([ { keys: "[TouchA]" target: login }, { keys: "[/TouchA]", target: login } ])}); #### Validating assertions `vitest` comes with the `expect` function to facilitate assertions that works like: expect(subject)[assertion](value) The command supports assertions like `toBe` (reference comparison) and `toEqual` (value comparison) out of the box. For testing inside the DOM, the package `@testing-library/jest-dom` augments it with some helpful additional assertions: * `.toBeInTheDocument()` - checks if the element actually exists in the DOM * `.toBeVisible()` - checks if there is no reason the element should be hidden * `.toHaveTextContent(content)` - checks if the text content matches * `.toHaveFocus()` - checks if this is the currently focused element * `.toHaveAccessibleDescription(description)` - checks accessible description * and a lot more. ### Directive testing Directives are reusable behaviors for elements. They receive the HTML element they are bound to as their first and an accessor of the directive prop as their second argument. To make testing them more concise, `@solidjs/testing-library` has a `renderDirective` function: const renderResult = renderDirective(directive, { initialValue, // value initially added to the argument signal targetElement, // opt. node name or element used as target for the directive ...renderOptions, // see render options})const { arg, // getter for the directive's argument setArg, // setter for the directive's argument ...renderResults, // see render results} = renderResult In `...renderResults`, the container will contain the `targetElement`, which defaults to a `<div>`. This, along with the ability to modify the `arg` signal, are helpful when testing directives. If, for example, you have a directive that handles the Fullscreen API, you can test it like this: import { test, expect, vi } from "vitest"import { renderDirective } from "@solidjs/testing-library"import { createFullScreen } from "./fullscreen"test("toggles fullscreen", () => { const targetElement = document.createElement("div") const fs = vi.spyOn(targetElement, "fullscreen") const [setArg, container] = renderDirective(createFullScreen, false) setArg(true) expect(fs).toHaveBeenCalled()}) import { Accessor } from "solid-js"export const fullscreen = (ref: HTMLElement, active: Accessor<boolean>) => createEffect(() => { const isActive = document.fullscreenElement === ref if (active() && !isActive) { ref.requestFullScreen().catch(() => {}) } else if (!active() && isActive) { document.exitFullScreen() } }) ### Primitive testing When the reference to an element is not needed, parts of state and logic can be put into reusable hooks or primitives. Since these do not require elements, there is no need for `render` to test them since it would require a component that has no other use. To avoid this, there is a `renderHook` utility that simulates a component without actually rendering anything. const renderResult = renderHook(hook, { initialProps, // an array with arguments being supplied to the hook wrapper, // same as the wrapper optionss for `render`})const { result, // return value of the hook (mutable, destructuring fixes it) cleanup, // manually remove the traces of the test from the DOM owner, // the owner running the hook to use with `runWithOwner()`} = renderResult A primitive that manages the state of a counter could be tested like this: import { test, expect } from "vitest"import { renderHook } from "@solidjs/testing-library"import { createCounter } from "./counter"test("increments count", () => { const { result } = renderHook(createCounter) expect(result.count).toBe(0) result.increment() expect(result.count).toBe(1)}) ### Testing effects Since effects may happen asynchronously, it can be difficult to test them. `@solidjs/testing-library` comes with a `testEffect` function that takes another function that receives a `done` function to be called once tests are over and returns a promise. Once `done` is called, the returned promise is resolved. Any errors that would hit the next boundary are used to reject the returned promise. An example test using `testEffect` may look like this: const [value, setValue] = createSignal(0)return testEffect(done => createEffect((run: number = 0) => { if (run === 0) { expect(value()).toBe(0) setValue(1) } else if (run === 1) { expect(value()).toBe(1) done() } return run + 1 })) ### Benchmarks While Solid offers performance simplified, it is good to validate if that promise can be kept. Vitest offers an experimental `bench` function to run benchmarks and compare the results inside the same `describe` block; for example if you had a `<List>` flow component similar to `<For>`, you could benchmark it like this: describe('list rendering', () => { const ITEMS = 1000 const renderedFor = new Set() const listFor = Array.from({ length: ITEMS }, (_, i) => i) bench('For', () => new Promise((resolve) => { const ItemFor = (props) => { onMount(() => { renderedFor.add(props.number) if (renderedFor.size === ITEMS) { resolve() } }) return <span>{props.number}</span> } render(() => <For each={listFor}> {(item) => <ItemFor number={item} />} </For>) })) const renderedList = new Set() const listList = Array.from({ length: ITEMS }, (_, i) => i) bench('List', () => new Promise((resolve) => { const ItemList = (props) => { onMount(() => { renderedList.add(props.number) if (renderedList.size === ITEMS) { resolve() } }) return <span>{props.number}</span> } render(() => <List each={listList}> {(item) => <ItemList number={item} />} </List>) }))}) Running `[npm|pnpm|yarn] test bench` will then execute the benchmark function: [RUN] v1.4.0 solid-app/src/components/ ✓ src/components/list.bench.jsx (2) 1364ms ✓ benchmark (2) 1360ms name hz min max mean p75 p99 p995 p999 rme samples · For 60.5492 11.2355 47.9164 16.5155 15.4180 47.9164 47.9164 47.9164 ±13.60% 31 fastest · List 49.7725 16.5441 69.3559 20.0914 18.0349 69.3559 69.3559 69.3559 ±21.37% 25[BENCH] SummaryFor - src/components/list.bench.tsx > benchmark 1.22x faster than List Please keep in mind that it is very difficult to create meaningful benchmarks. The numbers should always be taken with a grain of salt, but can still indicate performance degradations if compared between versions. ### Test coverage While coverage numbers can be misleading, they are used by many projects as a rough measurement of code quality. Vitest supports coverage collection. To use it, it needs an extra package: npm i @vitest/coverage-v8 -D pnpm i @vitest/coverage-v8 -D yarn add @vitest/coverage-v8 -D bun i @vitest/coverage-v8 -d deno add npm:@vitest/coverage-v8 -D Also, you need to set up vitest's coverage feature. ### Integration/E2E testing Some issues can only be found once the code is running in the environment it is supposed to run in. Since integration and end-to-end tests are agnostic to frameworks, all proven approaches will work equally for Solid. --- ## Page: https://docs.solidjs.com/guides/deploying-your-app Are you ready to deploy your Solid application? Follow our guides for different deployment services.  Cloudflare  Firebase  AWS with Flightcontrol  AWS with SST  Netlify  Railway  Vercel  Stormkit  Zerops --- ## Page: https://docs.solidjs.com/guides/styling-components/sass SASS is a popular CSS preprocessor that makes authoring CSS easier. It is a superset of CSS and offers two syntaxes: SCSS and the indented syntax (often referred to as just "SASS"). * * * ## Installation Depending on your package manager, SASS can be installed as a development dependency: npm i sass -D pnpm i sass -D yarn add sass -D bun i sass -d deno add npm:sass -D * * * ## Convert filename extensions After installation, the `.css` filename extensions will have to be changed to `.scss` or `.sass`. The `.scss` syntax is a strict superset of CSS, while `.sass` offers a more relaxed syntax. Vite, which is integrated with Solid, supports both. However, `.scss` is generally recommended. // Card.scss.grid { display: grid; &-center { place-items: center; }}.screen { min-height: 100vh;}.card { height: 160px; aspect-ratio: 2; border-radius: 16px; background-color: white; box-shadow: 0 0 0 4px hsl(0 0% 0% / 15%);} In a Solid component: // Card.jsximport "./card.scss";function Card() { return ( <> <div class="grid grid-center screen"> <div class="card">Hello, world!</div> </div> </> );} By simply changing the file extension from `.css` to `.scss` or `.sass` , Vite will automatically recognize these files and compile SASS to CSS on demand. When building in production, all SASS files are converted to CSS. This ensures compatibility with most modern browsers. --- ## Page: https://docs.solidjs.com/guides/styling-components/less LESS is a preprocessor based on JavaScript. It provides the ability to use mixins, variables, and other programmatic tools, making styling code cleaner and less redundant. * * * ## Installation To utilize LESS in a Solid app, it will need to be installed as a development dependency: npm i less -D pnpm i less -D yarn add less -D bun i less -d deno add npm:less -D * * * ## Using LESS in your app Start by creating a `.less` file in the `src` directory: //styles.less.foo { color: red;}.bar { background-color: blue;} The basic syntax of LESS is very similar to CSS. However, LESS allows the declaration and usage of variables: //styles.less@plainred: red;@plainblue: blue;.foo { color: @plainred;}.bar { background-color: @plainblue;} To use these styles in a Solid component, import the `.less` file: //component.jsximport "./styles.less";function Component() { return ( <> <div class="foo bar">Hello, world!</div> </> );} By changing the file extension of the imported styles to `.less`, Vite will recognize it as a LESS file and compile it to CSS on demand. --- ## Page: https://docs.solidjs.com/guides/styling-components/css-modules CSS Modules are CSS files where class names, animations, and media queries are scoped locally by default. These provide a way to encapsulate styles within components, preventing global conflicts and optimizing the final output by bundling only the used selectors. * * * Begin by creating a CSS module file. Conventionally, these files have a `.module.css` extension, like `style.module.css`. However, you can also use other extensions, such as `.scss` and `.sass`. /* styles.module.css */.foo { color: red;}.bar { background-color: blue;} **Note:** Avoid the use of HTML tags in CSS modules. Since they are not considered pure selectors, it can lead to specificity issues which can make it more difficult to override with other styles and lead to unexpected behaviors. * * * 1. **Importing styles:** In your component file (eg. `Component.jsx`), import the styles from the CSS module. // component.jsximport styles from "styles.module.css"; 2. **Applying styles:** Use the imported styles by referencing them as properties of the styles object in your JSX: function Component() { return ( <> <div class={`${styles.foo} ${styles.bar}`}>Hello, world!</div> </> );} 3. **Using a single style:** If you only need one style from the module, import and apply it directly: // component.jsximport styles from "styles.module.css";function Component() { return ( <> <div class={styles.foo}>Hello, world!</div> </> );} 4. **Mixing with regular class names:** You can combine CSS module syntax with regular string class names, as well: // component.jsximport styles from "styles.module.css";function Component() { return ( <> <div class={`${styles.foo} container`}>Hello, world!</div> </> );} **Note:** If your styles have dashes in their names, use bracket notation: const className = styles["foo-with-dash"]; --- ## Page: https://docs.solidjs.com/guides/styling-components/macaron Macaron is compile-time CSS-in-JS library that offers type safety. * * * 1. Install and set up the macaron plugin for your bundler: npm i @macaron-css/core @macaron-css/solid pnpm i @macaron-css/core @macaron-css/solid yarn add @macaron-css/core @macaron-css/solid bun i @macaron-css/core @macaron-css/solid deno add npm:@macaron-css/core @macaron-css/solid 2. Within your `vite.config.js` folder, add the macaron plugin prior to other plugins: import { macaronVitePlugin } from "@macaron-css/vite";import { defineConfig } from "vite";export default defineConfig({ plugins: [ macaronVitePlugin(), // other plugins ],}); * * * 1. Import `styled` from `@macaron-css/solid` and create a styled component: // button.tsximport { styled } from "@macaron-css/solid";const Button = styled("button", {}); 2. Add styles that will be applied to the components by default: import { styled } from "@macaron-css/solid";const Button = styled("button", { base: { backgroundColor: "red", borderRadius: "10px", },}); Variants can be added using the `variants` key: import { styled } from "@macaron-css/solid";const Button = styled("button", { base: { backgroundColor: "red", borderRadius: "10px", }, variants: { color: { violet: { backgroundColor: "violet", }, gray: { backgroundColor: "gray", }, }, },}); Additionally, the `defaultVariants` feature is set to `variants` by default. This can be overridden at the time of usage: import { styled } from "@macaron-css/solid";const Button = styled("button", { base: { backgroundColor: "red", borderRadius: "10px", }, variants: { color: { violet: { backgroundColor: "violet", }, gray: { backgroundColor: "gray", }, }, }, defaultVariants: { color: "blue", },}); These components can be used like any other Solid component, with type-safe props derived from your variants. For more information on how to use macaron, visit their documentation. --- ## Page: https://docs.solidjs.com/guides/styling-components/tailwind Styling your ComponentsEdit this page Tailwind CSS is an on-demand utility CSS library that integrates seamlessly with Solid as a built-in PostCSS plugin. * * * 1. Install Tailwind CSS as a development dependency: npm i tailwindcss @tailwindcss/postcss postcss -D pnpm i tailwindcss @tailwindcss/postcss postcss -D yarn add tailwindcss @tailwindcss/postcss postcss -D bun i tailwindcss @tailwindcss/postcss postcss -d deno add npm:tailwindcss @tailwindcss/postcss postcss -D 2. Add `@tailwind/postcss` to the `plugins` in your PostCSS configuration. If you do not have a PostCSS configuration file, create a new one called `postcss.config.mjs`. export default { plugins: { "@tailwindcss/postcss": {}, }} For a deeper dive into configuration, you can check out the Tailwind Official Documentation. * * * Add an `@import` to your `src/index.css` file that imports Tailwind CSS. @import "tailwindcss"; * * * Import your `index.css` file into the root `index.jsx` or `index.tsx` file: import { render } from "solid-js/web"import App from "./App"import "./index.css"render(() => <App />, document.getElementById('root') as HTMLElement); * * * With Tailwind CSS set up, you can now utilize its utility classes. For instance, if you previously had a `Card.css` file, you can replace or remove it: /* src/components/Card.css *//* Remove or replace these styles with Tailwind utility classes */ Update your components to use Tailwind's utility classes: /* src/components/Card.jsx */function Card() { return ( <div class="grid place-items-center min-h-screen"> <div class="h-[160px] aspect aspect-[2] rounded-[16px] shadow-[0_0_0_4px_hsl(0_0%_0%_/_15%)]"> Hello, world! </div> </div> );} * * * For additional assistance, refer to the Tailwind CSS/Vite integration guide. Report an issue with this page --- ## Page: https://docs.solidjs.com/guides/styling-components/uno UnoCSS is an on-demand utility CSS library that integrates seamlessly with Solid as a Vite plugin. * * * To get started with UnoCSS in your Solid app: npm i unocss -D pnpm i unocss -D yarn add unocss -D bun i unocss -d deno add npm:unocss -D * * * After installation, open your `vite.config.js` or `vite.config.ts`. The default Solid Vite configuration looks like this: import { defineConfig } from "vite";import solidPlugin from "vite-plugin-solid";export default defineConfig({ plugins: [solidPlugin()], server: { port: 3000, }, build: { target: "esnext", },}); Now, import `unocssPlugin` from "unocss/vite" and add it to the plugins array: import { defineConfig } from "vite";import unocssPlugin from "unocss/vite";import solidPlugin from "vite-plugin-solid";export default defineConfig({ plugins: [unocssPlugin(), solidPlugin()], server: { port: 3000, }, build: { target: "esnext", },}); Ensure that `unocssPlugin` is ordered before `solidPlugin` to prevent certain edge cases. * * * In your root `index.jsx` or `index.tsx` file, import UnoCSS: /* @refresh reload */import "uno.css"import { render } from "solid-js/web"import "./index.css"import App from "./App"render(() => <App />, document.getElementById('root') as HTMLElement); Alternatively, you can use the alias `import "virtual:uno.css"`: /* @refresh reload */import "virtual:uno.css"import { render } from "solid-js/web"import "./index.css"import App from "./App"render(() => <App />, document.getElementById('root') as HTMLElement); #### Support For additional assistance, refer to the UnoCSS/Vite integration guide . --- ## Page: https://docs.solidjs.com/guides/deployment-options/cloudflare Cloudflare Pages is a JAMstack platform for frontend developers, where JAMstack stands for JavaScript, APIs, and Markup. For additional details and features, you can visit the Cloudflare website. * * * 1. Navigate to the Cloudflare login page and log in or sign up. 2. After logging in, find "Pages" in the left-hand navigation bar. Add a new project by clicking "Create a project," then choose "Connect to Git." 3. You'll have the option to install Cloudflare Pages on all your repositories or select ones. Choose the repository that contains your Solid project. 4. Configure your build settings: * The project name will default to the repository name, but you can change it if you wish. * In the "build command" field, enter `npm run build` . * For the "build output directory" field, use `dist` . * Add an environment variable `NODE_VERSION` and set its value to the version of Node.js you're using. **Note:** This step is crucial because Cloudflare Pages uses a version of Node.js older than v13, which may not fully support Vite, the bundler used in Solid projects. 5. Once you've configured the settings, click "Save and Deploy." In a few minutes, your Solid project will be live on Cloudflare Pages, accessible via a URL formatted as `project_name.pages.dev`. * * * Wrangler is a command-line tool for building Cloudflare Workers. Here are the steps to deploy your Solid project using Wrangler. 1. Use your package manager of choice to install the Wrangler command-line tool: npm i wrangler -g pnpm i wrangler -g yarn add wrangler -g bun i wrangler -g deno add npm:wrangler -g 2. Open your terminal and run the following command to log in: wrangler login 3. Build your project using the following command: npm run build pnpm build yarn build bun run build deno run build 4. Deploy using Wrangler: wrangler pages publish dist After running these commands, your project should be live. While the terminal may provide a link, it's more reliable to check your Cloudflare Pages dashboard for the deployed URL, which usually follows the format `project-name.pages.dev`. **Note:** Make sure to navigate to the `Speed` -> `Optimization settings` section in your Cloudflare website dashboard and disable the `Auto Minify` option. This is important as minification and comment removal can interfere with hydration. --- ## Page: https://docs.solidjs.com/guides/deployment-options/firebase Firebase is an all-in-one app development platform by Google, offering a range of services from real-time databases to user authentication. For a detailed overview of the services available, you can visit Firebase's documentation. Before proceeding, make sure you've already set up a project in your Firebase console. If you haven't, you can follow Firebase's official guide to create a new Firebase project. * * * 1. Use your preferred package manager to install the Firebase command-line tool with one of the following commands: npm i firebase-tools -g pnpm i firebase-tools -g yarn add firebase-tools -g bun i firebase-tools -g deno add npm:firebase-tools -g 2. Execute the `firebase login` command to ensure that you're logged into the Firebase account associated with your project. 3. In the root directory of your Solid project, create two new files: `firebase.json` and `.firebaserc`. * In `firebase.json`, add the following code: { "hosting": { "public": "dist", "ignore": [] }} * In `.firebaserc`, insert the following code (replace `<YOUR_FIREBASE_PROJECT_ID>` with your Firebase project ID): { "projects": { "default": "<YOUR_FIREBASE_PROJECT_ID>" }} 4. Run `npm run build` , followed by `firebase deploy` to build and deploy your project. Upon completion, a `Hosting URL` will be displayed, indicating the live deployment of your project. --- ## Page: https://docs.solidjs.com/guides/deployment-options/aws-via-flightcontrol Flightcontrol is a platform that fully automates deployments to Amazon Web Services (AWS). For more information on Flightcontrol's capabilities, you can visit their docs. * * * Flightcontrol offers a GitHub integration, leveraging its continuous development actions. To get started with Flightcontrol's GitHub integration, you'll first need to log in or sign up to the Flightcontrol platform. After you're logged in, simply link your GitHub account to Flightcontrol. Once connected, Flightcontrol will take care of the rest. It automatically detects any new pushes to your specified GitHub branches and builds your project. The build process uses the commands in your `package.json` file and adheres to the settings that you have configured in Flightcontrol. No additional setup is needed. * * * 1. In the Flightcontrol dashboard, create a new project and select the repository you wish to use as the source. 2. Choose the GUI as your configuration type. 3. Add your Solid site as a static site by clicking the "Add a Static Site" option. 5. Label your output directory as `dist`. 6. If your project requires environment variables, add them in the designated area: 7. Finally, connect your AWS account to complete the setup. * * * 1. Navigate to your Flightcontrol dashboard and initiate a new project. Choose the repository you'd like to use as the source. 2. Opt for the `flightcontrol.json` as your configuration type. 3. Add a new file named `flightcontrol.json` at the root of your selected repository. Below is an example configuration: { "$schema": "https://app.flightcontrol.dev/schema.json", "environments": [ { "id": "production", "name": "Production", "region": "us-west-2", "source": { "branch": "main" }, "services": [ { "id": "my-static-solid", "buildType": "nixpacks", "name": "My static solid site", "type": "static", "domain": "solid.yourapp.com", "outputDirectory": "dist", "singlePageApp": true } ] } ]} --- ## Page: https://docs.solidjs.com/guides/deployment-options/aws-via-sst SST is a framework for deploying applications to any cloud provider. It has a built-in way to deploy SolidStart apps to AWS Lambda. For additional details, you can visit their docs. * * * 1. Create a SolidStart app. 2. In your project, init SST. npx sst@latest init pnpx sst@latest init yarn dlx sst@latest init bunx sst@latest init dpx sst@latest init 3. This will detect your SolidStart app and ask you to update your `app.config.ts`. server: { preset: "aws-lambda-streaming"} 4. When you are ready, you can deploy your app using: npx sst@latest deploy --stage production pnpx sst@latest deploy --stage production yarn dlx sst@latest deploy --stage production bunx sst@latest deploy --stage production dpx sst@latest deploy --stage production You can read the full tutorial on the SST docs. * * * You can also deploy your SolidStart app to a container using SST. --- ## Page: https://docs.solidjs.com/guides/deployment-options/netlify Deploying your AppEdit this page Netlify is a widely-used hosting platform suitable for various types of projects. For detailed guidance on build procedures, deployment options, and the range of features available, you can visit the Netlify documentation. * * * 1. Begin by navigating to Netlify's website and logging in or creating a new Netlify. Once logged in, you will be take to your dashboard. Click the `New site from Git` button to start a new project. 2. On the following page, choose "Connect to GitHub" or your preferred Git repository hosting service. 3. After selecting your Solid project repository, you'll be directed to a configuration screen. Update the "Publish directory" field from `netlify` to `dist`. Then, click "Deploy" to start the deployment process. 4. Once the build and deployment are complete, you will be taken to a screen that displays the URL of your live site. * * * 1. Install the Netlify CLI using your preferred package manager: npm i netlify-cli -g pnpm i netlify-cli -g yarn add netlify-cli -g bun i netlify-cli -g deno add npm:netlify-cli -g **Note:** Before proceeding, ensure that your Netlify account and team are fully set up. This is crucial for a seamless project setup and deployment. 2. Open your terminal, navigate to your project directory, and run the `netlify init` command. Authenticate using one of the supported login options. 3. Follow the on-screen instructions from the CLI. When prompted for the 'Directory to deploy,' specify `dist` — this is where Solid stores the built project files. After completing the process, your project will be deployed on Netlify and can be accessed via the provided URL. Report an issue with this page --- ## Page: https://docs.solidjs.com/guides/deployment-options/railway Deploying your AppEdit this page Railway is a well-known platform for deploying a variety of web and cloud-based projects. For an in-depth look at the features offered by Railway, as well as detailed deployment guidelines, you can consult the Railway documentation. * * * To begin, you need to update the start command in your `package.json` file to make it compatible with Railway. Change the start command to `npx http-server ./dist` instead of using `vite`. This adjustment means you will need to build the app to generate the `dist` folder. For local development, continue using the original `dev` command. Reserve the modified start command specifically for Railway deployments. Below is an example of how your `package.json` may be configured: "scripts": { "start": "npx http-server ./dist", "dev": "vite", "build": "vite build", "serve": "vite preview", "predeploy": "npm run build", "deploy": "gh-pages -d build"}, * * * 1. Visit Railway's homepage and click "Start a New Project." You will be redirected to connect with GitHub. Log in or create an account using your GitHub credentials and authorize Railway to access your account. 2. After authorization, choose the repository that has your Solid project. During this step, you can also add any required environment variables. 3. Once your project is configured, click "Deploy Now." After a successful deployment, a confirmation screen will appear. 4. Railway does not automatically assign a domain to your project. To do this, go to the settings and manually generate a domain for your deployed project. Once a domain has been generated, your Solid project should be live. * * * 1. Using your preferred package manager and install the Railway CLI: npm i @railway/cli -g pnpm i @railway/cli -g yarn add @railway/cli -g bun i @railway/cli -g deno add npm:@railway/cli -g 2. Open your terminal and run the following command to log in: railway login 3. You have the option to link your local Solid project to an existing Railway project using railway link. Alternatively, you can create a new project with `railway init` and follow the on-screen prompts. 4. To deploy your project to Railway, use the following command: railway up# orrailway up --detach # if you prefer to avoid logs Your project will now be live on Railway. Report an issue with this page --- ## Page: https://docs.solidjs.com/guides/deployment-options/vercel Vercel is a widely-used platform specialized in hosting frontend projects. For detailed information regarding build and deployment instructions, as well as features they offer, please visit the Vercel documentation. * * * ## Using Vercel web interface 1. Navigate to vercel.com/login to log in or create a new account. Connect with your preferred Git repository hosting service. 2. Once on the dashboard, click the button at the top right corner and choose "Add New Project." On the next page, select "Continue with GitHub" or your preferred Git service. 3. You will then see with a list of your repositories. Use the search bar if needed to find the specific repository you want to deploy. Click the "Import" button to proceed. 4. After importing your Solid project repository, you will be taken to a configuration screen. If your project requires any environment variables, add them in the designated field. Click "Deploy" to start the deployment process. 5. Once the build and deployment are finished, you will be redirected to a screen that displays a screenshot of your live site. * * * ## Using the Vercel CLI 1. Install the Vercel CLI using your preferred package manager. npm i vercel -g pnpm i vercel -g yarn add vercel -g bun i vercel -g deno add npm:vercel -g 2. Open your terminal, navigate to your project directory, and run the following command to log in: vercel 3. Follow the on-screen instructions from the CLI to finalize the deployment. Once completed, your project will be live on Vercel and accessible via the provided URL. --- ## Page: https://docs.solidjs.com/guides/deployment-options/stormkit Stormkit is a deployment platform for static websites, single-page applications (SPAs), and serverless functions. 1. Log in to Stormkit. 2. Using the user interface, import your Solid project from one of the three supported Git providers (GitHub, GitLab, or Bitbucket). 3. Navigate to the project’s production environment in Stormkit or create a new environment if needed. 4. Verify the build command in your Stormkit configuration. By default, Stormkit CI will run `npm run build` but you can specify a custom build command on this page. 5. Check output folder, unless its specified Stormkit will try to upload contents of build folder. 6. Click the “Deploy Now” button to deploy your site. Stormkit CI will build your code and upload contents of it. Find more details on Stormkit Documentation. --- ## Page: https://docs.solidjs.com/guides/deployment-options/zerops Zerops is a dev-first cloud platform that can be used to deploy both Static and SSR Solid Node.js Apps. For additional one-to-one support, details, and features, you can join the Zerops Discord server and visit the Zerops Docs. Deploy and test Zerops Solid recipes with one click: * Deploy Solid Node.js & Static Together - Node.js and Static. * Deploy Solid Node.js - Source Repository * Deploy Solid Static - Source Repository * * * 1. Go to Zerops Registration and sign up using GitHub, GitLab, or just your email. * * * There are two ways to set up a Zerops project and a service: #### Using Project Add Wizard (GUI) 1. Go to your Zerops dashboard. 2. Add a new project using your sidebar. If you're in compact mode, click on your profile and then "Add new project." 3. You'll be redirected to a page where you can choose a service. ##### For Static: 1. Choose Static. 2. Scroll down and change the hostname to your preference. 3. Scroll down and click on the "Add New Static" button. ##### For SSR - Node.js: 1. Choose `Node.js` and select `version 20`. 2. Scroll down and change the hostname to your preference. 3. Scroll down and click on the "Add New Node.js" button. #### Using Project Import YAML **Note**: This is only used for project creation using YAML on the web interface—no need to add it to the project. 1. Go to your Zerops dashboard and click on your profile icon if you are a new user. If not, check your sidebar and click on `Import Project`. ##### Static: project: name: recipe-solidjsservices: - hostname: app type: static enableSubdomainAccess: true ##### SSR - Node.js: project: name: recipe-solidjsservices: - hostname: app type: nodejs@20 enableSubdomainAccess: true * * * The `zerops.yml` configuration file is used to tell Zerops how to build and run your application, it should be placed to the root of your appplication's repository. Example for **SSR (Server-Side Rendering)** Apps: Set up the `zerops.yml` file in the root of your SSR project. Make sure the setup parameter's value is the same as the hostname of the service. zerops: - setup: app build: base: nodejs@latest buildCommands: - pnpm i - pnpm build deployFiles: - .output - node_modules - public - package.json run: base: nodejs@latest ports: - port: 3000 httpSupport: true start: pnpm start Example for **SSG (Static Site Generation)** Apps: Set up the `zerops.yml` file in the root of your SSG project. Make sure the setup parameter's value is the same as the hostname of the service. zerops: - setup: app build: base: nodejs@latest buildCommands: - pnpm i - pnpm build deployFiles: - dist/~ run: base: static Push the changes to your GitHub/GitLab repository (necessary if you are planning to use GitHub/GitLab). * * * ### Triggering the pipeline automatically by connecting Github/Gitlab repository You can push your project by Triggering the pipeline using Zerops CLI or by connecting the app service with your GitHub / GitLab repository from inside the service detail. ### Triggering the pipeline manually using Zerops CLI To download the zCLI binary directly, use zCLI/releases or: 1. Install the Zerops CLI using Terminal. Linux/MacOS curl -L https://zerops.io/zcli/install.sh | sh Windows irm https://zerops.io/zcli/install.ps1 | iex Npm npm i @zerops/zcli -g pnpm i @zerops/zcli -g yarn add @zerops/zcli -g bun i @zerops/zcli -g deno add npm:@zerops/zcli -g 2. Open Settings > Access Token Management in the Zerops app and generate a new access token. 3. Log in using your access token with the following command: zcli login <token> 4. Navigate to the root of your app (where zerops.yml is located) and run the following command in Terminal to trigger the deploy: zcli push Check the official docs if you need more advanced use-cases for Zerops Docs. --- ## Page: https://docs.solidjs.com/guides/styling-components/tailwind-v3 Tailwind CSS v3 is an on-demand utility CSS library that integrates seamlessly with Solid as a built-in PostCSS plugin. * * * 1. Install Tailwind CSS as a development dependency: npm i tailwindcss@3 postcss autoprefixer -D pnpm i tailwindcss@3 postcss autoprefixer -D yarn add tailwindcss@3 postcss autoprefixer -D bun i tailwindcss@3 postcss autoprefixer -d deno add npm:tailwindcss@3 postcss autoprefixer -D 2. Next, run the init command to generate both `tailwind.config.js` and `postcss.config.js`. npx tailwindcss init -p pnpx tailwindcss init -p yarn dlx tailwindcss init -p bunx tailwindcss init -p dpx tailwindcss init -p 3. Since Tailwind CSS is configuration-driven, after initializing, a `tailwind.config.js` file will be created at the root of your project directory: /** @type {import('tailwindcss').Config} */module.exports = { content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: {}, }, plugins: [],}; For a deeper dive into configuration, you can check out the Tailwind Official Documentation. * * * In your `src/index.css` file, add the following Tailwind directives: @tailwind base;@tailwind components;@tailwind utilities; These directives inform PostCSS that you're using Tailwind and establish the order of the directives. You can append custom CSS below these directives. * * * Import your `index.css` file into the root `index.jsx` or `index.tsx` file: import { render } from "solid-js/web"import App from "./App"import "./index.css"render(() => <App />, document.getElementById('root') as HTMLElement); * * * With Tailwind CSS set up, you can now utilize its utility classes. For instance, if you previously had a `Card.css` file, you can replace or remove it: /* src/components/Card.css *//* Remove or replace these styles with Tailwind utility classes */ Update your components to use Tailwind's utility classes: /* src/components/Card.jsx */function Card() { return ( <div class="grid place-items-center min-h-screen"> <div class="h-[160px] aspect aspect-[2] rounded-[16px] shadow-[0_0_0_4px_hsl(0_0%_0%_/_15%)]"> Hello, world! </div> </div> );} * * * For additional assistance, refer to the Tailwind CSS/Vite integration guide.