Skip to content

How to Share Styles in Web Components with LitElement and TypeScript

Have you ever thought about the benefits of code reuse?

It is common to try to find techniques or strategies that allow us to save time and reuse existing code. However, let's start by first understanding what we mean by code reuse.

Code reuse, also called software reuse, is the use of existing software, or software knowledge, to build new software, following the reusability principles.

According to this definition, we may think of code blocks in different levels: development scripts, software components, testing suites, templates, styles, etc.

In this article, we'll deep dive over the Web Components creation to understand how to reuse the styles through LitElement and TypeScript.

A Simple Web Component

Let's get started creating a basic component to describe the main parts of it.

import { LitElement, html, customElement, css } from "lit-element";

@customElement("corp-button")
export class CorpButton extends LitElement {
  static styles = css`
    .corp-button {
      background-color: #e7e7e7;
      color: black;
      border: none;
      border-radius: 4px;
      padding: 15px 32px;
      font-size: 16px;
      margin: 4px 2px;
      cursor: pointer;
    }
    .corp-button:hover {
      background-color: #aaaaaa;
    }
  `;

  render() {
    return html`
      <button class="corp-button"><slot></slot></button>
    `;
  }
}

In the above code snippet, there is a new class CorpButton, which extends from the LitElement base-class. However, one important part here is the use of the @customElement decorator that allows us to register our brand new component <corp-button></corp-button> in a compact definition.

On other hand, the render function returns the HTML content to be rendered by the component. This template is still using JavaScript notation since a template literal is being used via lit-html, and that means it can include JavaScript expressions inside. How cool is that?

Also, pay attention to the styles defined, using a tagged template literal (again), and the css tag function. The styles property has been defined using the static keyword for performance reasons, and it makes sense thinking it will be applied to a class level rather than every instance of it.

That's all for this initial component, and it's ready to be rendered using the following notation.

<corp-button>Click Me</corp-button>

Sharing Styles

Now, let's suppose we're building another web component, and for some reason, we're going to need the same styles. Then, it's a good opportunity to apply the code reuse principles.

Style Inheritance

Inheritance is a core concept of any Object-Oriented Programming. Since we're using TypeScript, we can take advantage of it derive a class from CorpButton as follows.

import { html, customElement, css } from "lit-element";
import { CorpButton } from "./corp-button";

@customElement("corp-advanced-button")
class CorpAdvancedButton extends CorpButton {
  static get styles() {
    return [
      super.styles,
      css`
        .corp-button:hover {
          background-color: #008cba;
          color: white;
        }
      `
    ];
  }

  render() {
    return html`
      <button class="corp-button"><slot></slot></button>
    `;
  }
}

In the above example, we do the following:

  • The new class extends from CorpButton(the previous class-component) instead of LitElement. This means the CorpButton class can inherit attributes and behavior from its parent class.
  • Instead of defining a static property styles as the parent component does, it creates a static function instead to be able to perform a call to super.styles, and get access to its parent styles.

This is perfectly doable because the styles property can be a tagged template literal or even an array of them.

static get styles() {
  return [ css`...`, css`...`];
}

Style Modules

If you're more in favor of implementing composition over inheritance, there's another technique to share styles between components.

The first step is to identify the styles that need to be shared. Then, you can move forward by creating a TypeScript module to contain the common styles.

// styles/styles-button.ts

import { css } from "lit-element";

export const corpStylesButton = css`
  .corp-button {
    background-color: #e7e7e7;
    color: black;
    border: none;
    border-radius: 4px;
    padding: 15px 32px;
    font-size: 16px;
    margin: 4px 2px;
    cursor: pointer;
  }
  .corp-button:hover {
    background-color: #aaaaaa;
  }
`;

To implement this module, we created a new folder /button/styles. Then, the styles-button.ts file contains the exported styles using, again, the tagged styles notation.

The previous definition can be imported in your new component as follows.

import { LitElement, html, customElement, css } from "lit-element";
import { corpStylesButton } from "./styles/styles-button";

@customElement("corp-advanced-button")
class CorpAdvancedButton extends LitElement {
  static styles = [
    corpStylesButton,
    css`
      .corp-button:hover {
        background-color: #008cba;
        color: white;
      }
    `
  ];

  render() {
    return html`
      <button class="corp-button"><slot></slot></button>
    `;
  }
}

In this case, we just imported the styles module, and the styles property gets updated with an array of tagged template literals:

static styles = [
    corpStylesButton,
    css`...`
  ];

This is a powerful feature since it can help us import several styles of modules as required by the new component.

Please note the use of the styles as a property, and a method in the above examples.

Live Demo

Want to play around with this code? Just open the Stackblitz editor:

Feel free to reach out on Twitter if you have any questions. Follow me on GitHub to see more about my work.

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

I Broke My Hand So You Don't Have To (First-Hand Accessibility Insights) cover image

I Broke My Hand So You Don't Have To (First-Hand Accessibility Insights)

We take accessibility quite seriously here at This Dot because we know it's important. Still, throughout my career, I've seen many projects where accessibility was brushed aside for reasons like "our users don't really use keyboard shortcuts" or "we need to ship fast; we can add accessibility later." The truth is, that "later" often means "never." And it turns out, anyone could break their hand, like I did. I broke my dominant hand and spent four weeks in a cast, effectively rendering it useless and forcing me to work left-handed. I must thus apologize for the misleading title; this post should more accurately be dubbed "second-hand" accessibility insights. The Perspective of a Developer Firstly, it's not the end of the world. I adapted quickly to my temporary disability, which was, for the most part, a minor inconvenience. I had to type with one hand, obviously slower than my usual pace, but isn't a significant part of a software engineer's work focused on thinking? Here's what I did and learned: - I moved my mouse to the left and started using it with my left hand. I adapted quickly, but the experience wasn't as smooth as using my right hand. I could perform most tasks, but I needed to be more careful and precise. - Many actions require holding a key while pressing a mouse button (e.g., visiting links from the IDE), which is hard to do with one hand. - This led me to explore trackpad options. Apart from the Apple Magic Trackpad, choices were limited. As a Windows user (I know, sorry), that wasn't an option for me. I settled for a cheap trackpad from Amazon. A lot of tasks became easier; however, the trackpad eventually malfunctioned, sending me back to the mouse. - I don't know a lot of IDE shortcuts. I realized how much I've been relying on a mouse for my work, subconsciously refusing to learn new keyboard shortcuts (I'll be returning my senior engineer license shortly). So I learned a few new ones, which is good, I guess. - Some keyboard shortcuts are hard to press with one hand. If you find yourself in a similar situation, you may need to remap some of them. - Copilot became my best friend, saving me from a lot of slow typing, although I did have to correct and rewrite many of its suggestions. The Perspective of a User As a developer, I was able to get by and figure things out to be able to work effectively. As a user, however, I got to experience the other side of the coin and really feel the accessibility (or lack thereof) on the web. Here are a few insights I gained: - A lot of websites apparently tried_ to implement keyboard navigation, but failed miserably. For example, a big e-commerce website I tried to use to shop for the aforementioned trackpad seemed to work fine with keyboard navigation at first, but once I focused on the search field, I found myself unable to tab out from it. When you make the effort to implement keyboard navigation, please make sure it works properly and it doesn't get broken with new changes. I wholeheartedly recommend having e2e tests (e.g. with Playwright) that verify the keyboard navigation works as expected. - A few websites and web apps I tried to use were completely unusable with the keyboard and were designed to be used with a mouse only. - Some sites had elaborate keyboard navigation, with custom keyboard shortcuts for different functionality. That took some time to figure out, and I reckon it's not as intuitive as the designers thought it would be. Once a user learns the shortcuts, however, it could make their life easier, I suppose. - A lot of interactive elements are much smaller than they should be, making it hard to accurately click on them with your weaker hand. Designers, I beg you, please make your buttons bigger. I once worked on an application that had a "gloves mode" for environments where the operators would be using gloves, and I feel like maybe the size we went with for the "gloves mode" should be the standard everywhere, especially as screens get bigger and bigger. - Misclicking is easy, especially using your weaker hand. Be it a mouse click or just hitting an Enter key on accident. Kudos to all the developers who thought about this and implemented a confirmation dialog or other safety measures to prevent users from accidentally deleting or posting something. I've however encountered a few apps that didn't have any of these, and those made me a bit anxious, to be honest. If this is something you haven't thought about when developing an app, please start doing so, you might save someone a lot of trouble. Some Second-Hand Insights I was only a little bit impaired by being temporarily one-handed and it was honestly a big pain. In this post, I've focused on my anecdotal experience as a developer and a user, covering mostly keyboard navigation and mouse usage. I can only imagine how frustrating it must be for visually impaired users, or users with other disabilities, to use the web. I must confess I haven't always been treating accessibility as a priority, but I've certainly learned my lesson. I will try to make sure all the apps I work on are accessible and inclusive, and I will try to test not only the keyboard navigation, ARIA attributes, and other accessibility features, but also the overall experience of using the app with a screen reader. I hope this post will at least plant a little seed in your head that makes you think about what it feels like to be disabled and what would the experience of a disabled person be like using the app you're working on. Conclusion: The Humbling Realities of Accessibility The past few weeks have been an eye-opening journey for me into the world of accessibility, exposing its importance not just in theory but in palpable, daily experiences. My short-term impairment allowed me to peek into a life where simple tasks aren't so simple, and convenient shortcuts are a maze of complications. It has been a humbling experience, but also an illuminating one. As developers and designers, we often get caught in the rush to innovate and to ship, leaving behind essential elements that make technology inclusive and humane. While my temporary disability was an inconvenience, it's permanent for many others. A broken hand made me realize how broken our approach towards accessibility often is. The key takeaway here isn't just a list of accessibility tips; it's an earnest appeal to empathize with your end-users. "Designing for all" is not a checkbox to tick off before a product launch; it's an ongoing commitment to the understanding that everyone interacts with technology differently. When being empathetic and sincerely thinking about accessibility, you never know whose life you could be making easier. After all, disability isn't a special condition; it's a part of the human condition. And if you still think "Our users don't really use keyboard shortcuts" or "We can add accessibility later," remember that you're not just failing a compliance checklist, you're failing real people....

Testing a Fastify app with the NodeJS test runner cover image

Testing a Fastify app with the NodeJS test runner

Introduction Node.js has shipped a built-in test runner for a couple of major versions. Since its release I haven’t heard much about it so I decided to try it out on a simple Fastify API server application that I was working on. It turns out, it’s pretty good! It’s also really nice to start testing a node application without dealing with the hassle of installing some additional dependencies and managing more configurations. Since it’s got my stamp of approval, why not write a post about it? In this post, we will hit the highlights of the testing API and write some basic but real-life tests for an API server. This server will be built with Fastify, a plugin-centric API framework. They have some good documentation on testing that should make this pretty easy. We’ll also add a SQL driver for the plugin we will test. Setup Let's set up our simple API server by creating a new project, adding our dependencies, and creating some files. Ensure you’re running node v20 or greater (Test runner is a stable API as of the 20 major releases) Overview `index.js` - node entry that initializes our Fastify app and listens for incoming http requests on port 3001 `app.js` - this file exports a function that creates and returns our Fastify application instance `sql-plugin.js` - a Fastify plugin that sets up and connects to a SQL driver and makes it available on our app instance Application Code A simple first test For our first test we will just test our servers index route. If you recall from the app.js` code above, our index route returns a 501 response for “not implemented”. In this test, we're using the createApp` function to create a new instance of our Fastify app, and then using the `inject` method from the Fastify API to make a request to the `/` route. We import our test utilities directly from the node. Notice we can pass async functions to our test to use async/await. Node’s assert API has been around for a long time, this is what we are using to make our test assertions. To run this test, we can use the following command: By default the Node.js test runner uses the TAP reporter. You can configure it using other reporters or even create your own custom reporters for it to use. Testing our SQL plugin Next, let's take a look at how to test our Fastify Postgres plugin. This one is a bit more involved and gives us an opportunity to use more of the test runner features. In this example, we are using a feature called Subtests. This simply means when nested tests inside of a top-level test. In our top-level test call, we get a test parameter t` that we call methods on in our nested test structure. In this example, we use `t.beforeEach` to create a new Fastify app instance for each test, and call the `test` method to register our nested tests. Along with `beforeEach` the other methods you might expect are also available: `afterEach`, `before`, `after`. Since we don’t want to connect to our Postgres database in our tests, we are using the available Mocking API to mock out the client. This was the API that I was most excited to see included in the Node Test Runner. After the basics, you almost always need to mock some functions, methods, or libraries in your tests. After trying this feature, it works easily and as expected, I was confident that I could get pretty far testing with the new Node.js core API’s. Since my plugin only uses the end method of the Postgres driver, it’s the only method I provide a mock function for. Our second test confirms that it gets called when our Fastify server is shutting down. Additional features A lot of other features that are common in other popular testing frameworks are also available. Test styles and methods Along with our basic test` based tests we used for our Fastify plugins - `test` also includes `skip`, `todo`, and `only` methods. They are for what you would expect based on the names, skipping or only running certain tests, and work-in-progress tests. If you prefer, you also have the option of using the describe` → `it` test syntax. They both come with the same methods as `test` and I think it really comes down to a matter of personal preference. Test coverage This might be the deal breaker for some since this feature is still experimental. As popular as test coverage reporting is, I expect this API to be finalized and become stable in an upcoming version. Since this isn’t something that’s being shipped for the end user though, I say go for it. What’s the worst that could happen really? Other CLI flags —watch` - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--watch —test-name-pattern` - https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--test-name-pattern TypeScript support You can use a loader like you would for a regular node application to execute TypeScript files. Some popular examples are tsx` and `ts-node`. In practice, I found that this currently doesn’t work well since the test runner only looks for JS file types. After digging in I found that they added support to locate your test files via a glob string but it won’t be available until the next major version release. Conclusion The built-in test runner is a lot more comprehensive than I expected it to be. I was able to easily write some real-world tests for my application. If you don’t mind some of the features like coverage reporting being experimental, you can get pretty far without installing any additional dependencies. The biggest deal breaker on many projects at this point, in my opinion, is the lack of straightforward TypeScript support. This is the test command that I ended up with in my application: I’ll be honest, I stole this from a GitHub issue thread and I don’t know exactly how it works (but it does). If TypeScript is a requirement, maybe stick with Jest or Vitest for now 🙂...

How to Build a Slideshow App Using Swiper and Angular cover image

How to Build a Slideshow App Using Swiper and Angular

Google Slides and Microsoft PowerPoint are the most popular options to build presentations nowadays. However, have you ever considered having a custom tool where you can use your web development knowledge to create beautiful slides? In this post, we will build a slideshow app with the ability to render one slide at a time using Angular, Swiper, and a little bit of CSS. Project Setup Prerequisites You'll need to have installed the following tools in your local environment: - Node.js**. Preferably the latest LTS version. - A package manager**. You can use either NPM or Yarn. This tutorial will use NPM. Creating the Angular Project Let's start creating a project from scratch using the Angular CLI tool. `bash ng new slideshow-angular-swiper --routing --prefix corp --style scss --skip-tests ` This command will initialize a base project using some configuration options: - --routing`. It will create a routing module. - --prefix corp`. It defines a prefix to be applied to the selectors for created components(`corp` in this case). The default value is `app`. - --style scss`. The file extension for the styling files. - --skip-tests`. It avoids the generations of the `.spec.ts` files, which are used for testing. Creating the Slides Component We can create a brand new component to handle the content of the application's slides. We can do that using the command mg generate` as follows: `bash ng generate component slides ` The output of the previous command will show the auto-generated files. Update the Routing Configuration Remember we used the flag --routing` while creating the project? That parameter has created the main routing configuration file for the application: `app-routing.module.ts`. Let's update it to be able to render the `slides` component by default. `ts // app-routing.module.ts import { SlidesComponent } from './slides/slides.component'; const routes: Routes = [ { path: '', redirectTo: 'slides', pathMatch: 'full', }, { path: 'slides', component: SlidesComponent, }, ]; ` Update the App Component template Remove all code except the router-outlet` placeholder: `html ` This will allow you to render the slides` component by default once the routing configuration is running. Using Swiper What is Swiper? Swiper is a popular JavaScript library that lets you create transitions that can work on websites, mobile web apps, and mobile native/hybrid apps. It's available for all modern frameworks and it's powered with top-notch features you can find on the official website. Installing Swiper The plugin is available via NPM and you can use the following command to install it in the project. `bash npm install --save swiper ` Updating the Application Module Next, we'll need to update the application module before starting to use Swiper. `ts // app.module.ts import { SwiperModule } from 'swiper/angular'; @NgModule({ declarations: [ // ... ], imports: [ //... SwiperModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } ` Using swiper element in the Template It's time to work in the slide` component, and make use of the plugin along with the available options. `html {{ slide }} ` For a better understanding, let's describe what's happening in the above template: The `` element will create a Swiper instance to be rendered in the component. **slidesPerView** will set the number of slides visible at the same time. **navigation** will render the navigation buttons to slide to the left and right. **pagination** will set the pagination configuration through an object. **keyboard** will enable navigation through the keyboard. **virtual** will enable the virtual slides feature. This is important in case you have several slides, and you want to limit the amount of them to be rendered in the DOM. **class** will set a class to customize the styling. **swiperSlide** is an Angular directive that helps to render a slide instance. One important note here is the custom container created under the swiperSlide` directive allows us to customize the way we can render every slide. In this case, it's used to set a layout for every slide, and make sure to render it centered, and using the whole height of the viewport. Set the Swiper Configuration As you may note, the template will require additional configurations, and we'll need to create a couple of slides for the component. `ts // slides.component.ts import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import SwiperCore, { Keyboard, Pagination, Navigation, Virtual } from 'swiper'; SwiperCore.use([Keyboard, Pagination, Navigation, Virtual]); @Component({ selector: 'corp-slides', templateUrl: './slides.component.html', styleUrls: ['./slides.component.scss'], encapsulation: ViewEncapsulation.None, }) export class SlidesComponent implements OnInit { slides$ = new BehaviorSubject(['']); constructor() {} ngOnInit(): void { this.slides$.next( Array.from({ length: 600 }).map((el, index) => Slide ${index + 1}`) ); } } ` In the above code snippet, the component imports the SwiperCore` class along with the required modules. Next, the component needs to install those using a `SwiperCore.use([])` call. Later, a BehaviorSubject` is created to emit all slides content once the component gets initialized. Using Swiper Styles Since the current application is configured to use SCSS styles, we'll need to import the following styles into the slides.component.scss` file: `css / slides.component.scss */ @import 'swiper/scss'; @import 'swiper/scss/navigation'; @import 'swiper/scss/pagination'; ` Of course, these imports will work with the latest version of Swiper(version 8 at the time of writing this article). However, some users have found some issues while importing Swiper styles (mainly after doing an upgrade from Swiper v6 and v7). For example: `txt Error: Module not found: Can't resolve 'swiper/css' ` or an error like this: `txt Error: Module build failed (from ./nodemodules/sass-loader/dist/cjs.js): SassError: Can't find stylesheet to import. ` If you got any of those issues, you can give it a try with the following imports instead: `css / slides.component.scss */ @import 'swiper/swiper.min.css'; @import 'swiper/modules/navigation/navigation.min.css'; @import 'swiper/modules/pagination/pagination.min.css'; ` For the purpose of this demo, we'll attach additional styling to make it work: `css / slides.component.scss */ .my-swiper { height: 100%; } .swiper-slide-container { display: flex; justify-content: center; align-items: center; text-align: center; background: #fff; height: 100%; max-width: 600px; margin: auto; border-radius: 20px; } ` The .my-swiper` selector will set the appropriate height for every slide. On other hand, the `.swiper-slide-container` selector will provide a layout as a slide container. And this is how it will look on your web browser. Live Demo and Source Code Want to play around with the final application? Just open the following link in your browser: https://luixaviles.github.io/slideshow-angular-swiper. Find the complete angular project in this GitHub repository: slideshow-angular-swiper. Do not forget to give it a star ⭐️ and play around with the code. Feel free to reach out on Twitter if you have any questions. Follow me on GitHub to see more about my work....

Intro to DevRel: What's the Difference Between External and Internal DevRel Programs? cover image

Intro to DevRel: What's the Difference Between External and Internal DevRel Programs?

Developer Relations (DevRel__) is a proactive, multifaceted discipline that bridges the gap between developers and companies to drive adoption while cultivating an energetic and supportive developer community for their product, service, or technology. The term and the profession are often misunderstood even among those in other technical roles. Some have never heard of DevRel before, and others believe it’s a kind of tech support for developers. Many organizations even think starting a DevRel program means just giving away free software and hoping it catches on. But DevRel is none of these things. At its core, a successful DevRel program builds strong bonds within their target market to ensure that developers can interface with a company or organization behind the product they’re using. Great teams establish authentic connections with developers, cultivate trust, and actively engage with them. DevRel defies traditional marketing strategies. Instead of prioritizing numbers and eyes that contribute to a sales funnel, it focuses on enhancing developer satisfaction. This creates a feedback loop between users and a company to better meet their needs, and foster a sense of collaboration within a product’s user community. The Two Main Domains of Developer Relations DevRel is split into 2 main domains__: __external__ and __internal__. External: Accessing an existing developer community If a company already has a product with an existing community or a product that may appeal to an existing community, and they want to establish a DevRel program around it, this would fall within the external domain. A successful external program will establish credibility and support developers through a number of evangelistic measures like blog posts, tutorials, webinars, giving talks at meetups and conferences, or creating useful code examples to teach concepts. These activities and their goals are rarely product-specific. Instead, they incorporate a number of technologies within their product’s technical ecosystem to demonstrate its value to a developer’s workflow. I had the pleasure of working with Doron Sherman during his tenure at Cloudinary as VP of Developer Relations. Doron has extensive experience with building developer communities, and successfully advocated internally to build a website called Media Jams, a learning resource for developers working with media in their apps. By having this initiative live under Developer Relations, and not under Product, Marketing, or Engineering, Doron and his team built quickly and created a site that prioritized education, without needing to meet the business objectives of other parts of the organization. > “Media Jams has had great organic growth as a community resource. We were able to attract non-Cloudinary users as well as organic search traffic of those looking for media use cases who would have otherwise gotten lost in the Cloudinary docs and/or could not find help through the Cloudinary blog or knowledge base.” says Doron. Internal: Building a developer community In order to support the adoption and retention of developers using a product, companies must have a space where developers can interface with them. Building their own community around a product is the best way to do this. By creating open lines of communication, developers can provide immediate feedback about a product in a productive way to product and engineering teams thereby shortening the feedback loop and improving the speed at which a team is able to innovate based on user needs. This strategy falls under the internal domain. These forums also provide synergistic opportunities for developers that are using a product to learn from each other. By working on similar problems, developers are able to bond and feel more ownership or excitement toward a product, increasing user retention. Danny Thompson, a developer influencer and mentor who has built a community of over a quarter million followers, says that he admires Appwrite’s DevRel program, helmed by Tessa Mero, Head of Developer Relations: > “The Appwrite DevRel team is great at answering questions. They are on Discord, jumping on calls with developers, answering questions, and doing office hours, all of which are super valuable in building that community. The main difference between Appwrite DevRel and other teams is, a lot of communities are run very passively and not always available or taking an active approach within community forums to help out.” - Danny Thompson on Appwrite. > “When we think about how to become successful as a company through DevRel, our first consideration is, what made us successful in the first place? Appwrite became an open-source company and a successful open-source project because of community, so we focus on a community-first approach. Contributors and developers that have supported us since before we were a company are what led us to where we are now. Every initiative, every planning, and everything we do on our team, we consider the community's feedback and perspective before we make any decisions.” - Tessa Mero at Appwrite. The Value-First Approach to Developer Relations Successful DevRel programs prioritize delivering value to cultivate credibility among developers and support product adoption free from reciprocal demands. External efforts involve engaging with existing technology communities, establishing credibility through various evangelistic measures, and delivering value to the community. On the other hand, internal programs build communities around their product, facilitating direct communication between developers and the company. These internal forums not only enhance user retention but also foster a space for developers to learn from each other, creating a sense of ownership and excitement around the product. And by diverting equity to these two programs, DevRel teams find new users, retain them, and receive invaluable feedback. Real-world examples, such as Doron Sherman's work at Cloudinary and Tessa Mero's leadership at Appwrite, showcase the effectiveness of DevRel in action, and highlight how DevRel programs contribute to the success and sustainability of developer-focused products. In the ever-evolving landscape of technology, DevRel emerges not only as a bridge between developers and organizations, but as a crucial driver of innovation, ensuring products remain relevant, adaptive, and deeply integrated into the communities they serve. If you’re thinking about building a successful DevRel program for the first time, the best place to start is to reflect on some of your favorite brands and how they connect with the developer community. Do they simply distribute discount codes and free swag, or are they reaching out to their users, and providing them a platform to learn, collaborate with others, and contribute? If they are, what methods do they use, and how do those methods coincide with your team’s existing strengths? And if you ever have any questions or want to connect with a DevRel specialist, do not hesitate to reach out!...